Leetcode - 周赛421

目录

[一,3334. 数组的最大因子得分](#一,3334. 数组的最大因子得分)

[二,3335. 字符串转换后的长度 I](#二,3335. 字符串转换后的长度 I)

[三,3336. 最大公约数相等的子序列数量](#三,3336. 最大公约数相等的子序列数量)

[四,3337. 字符串转换后的长度 II](#四,3337. 字符串转换后的长度 II)


一,3334. 数组的最大因子得分

暴力方法就不演示,这里介绍一个O(n)的做法,前后缀分解,可以预处理nums数组的前缀GCD,LCM和后缀GCD,LCM。然后枚举移除的数,使用前后缀计算总体的GCD和LCM,然后计算出最大值。

代码如下:

class Solution {
    public long maxScore(int[] nums) {
        int n = nums.length;
        long[] sufGcd = new long[n+1];
        long[] sufLcm = new long[n+1];
        sufLcm[n] = 1;
        for(int i=n-1; i>=0; i--){
            sufGcd[i] = gcd(sufGcd[i+1], nums[i]);
            sufLcm[i] = lcm(sufLcm[i+1], nums[i]);
        }
        long res = sufGcd[0]*sufLcm[0], gc = 0, lc = 1;
        for(int i=0; i<n; i++){//枚举哪个不选
            res = Math.max(res, gcd(gc, sufGcd[i+1]) * lcm(lc, sufLcm[i+1]));
            lc = lcm(lc, nums[i]);
            gc = gcd(gc, nums[i]);
        }
        return res;
    }
    long gcd(long x, long y){//(0,x)的最大公约数就是x
        return y==0 ? x : gcd(y, x%y);
    }
    long lcm(long x, long y){//(1,x)的最小公倍数就是x
        return x*y/gcd(x, y);
    }
}

二,3335. 字符串转换后的长度 I

本题有多种做法,这里讲一个简单易懂的,可以发现对于每个字符,它们每次操作产生的字符数量是固定的,所以我们可以先统计26个字符,每个字符的出现次数cnt,然后模拟每次操作所能产生的字符数量,最后cnt数组的和就是答案。

代码如下:

class Solution {
    int MOD = 1_000_000_007;
    public int lengthAfterTransformations(String s, int t) {
        int ans = 0;
        int[] cnt = new int[26];
        for(char c : s.toCharArray()){
            cnt[c-'a']++;
        }
        while(t-- > 0){
            int[] a = cnt.clone();
            for(int i=1; i<26; i++){
                a[i] = cnt[i-1]%MOD;
            }
            a[0] = cnt[25]%MOD;
            a[1] = (a[1] + cnt[25])%MOD;
            cnt = a;
        }
        for(int i=0; i<26; i++){
            ans = (ans + cnt[i])%MOD;
        }
        return ans;
    }
}

三,3336. 最大公约数相等的子序列数量

本题是一道选或不选的问题,分情况讨论,对于第 i 个数:

  • 如果不选,即它既不在seq1中,也不在seq2中,接下来问题变成在第 i+1 个数到第 n-1 个数,有多少种分配使得子序列seq1和seq2的GCD相同
  • 如果选,分两种情况,它被分配到seq1/seq2中,接下来问题变成在第 i+1 个数到第 n-1 个数,有多少种分配使得子序列seq1和seq2的GCD相同

这样就不断的形成子问题,定义dfs(i,j,k):从第 i 个数到第 n-1 个数,且此时seq1的GCD为 j ,seq2的GCD为 k 时,使得子序列seq1和seq2的GCD相同的子序列对的总数。

  • 如果不选,问题变成在第 i+1 个数到第 n-1 个数,有多少种分配使得子序列seq1和seq2的GCD相同,即 dfs(i+1,j,k)
  • 如果选,分两种情况,它被分配到seq1/seq2中,问题变成在第 i+1 个数到第 n-1 个数,有多少种分配使得子序列seq1和seq2的GCD相同,即 dfs(i+1,gcd(j,nums[i]),k) + dfs(i+1, j, gcd(k,nums[i]),nums)
  • 求总对数 dfs(i,j,k) = dfs(i+1,j,k) + dfs(i+1,gcd(j,nums[i]),k) + dfs(i+1, j, gcd(k,nums[i]),nums)

代码如下:

class Solution {
    int MOD = 1_000_000_007;
    public int subsequencePairCount(int[] nums) {
        int n = nums.length;
        memo = new int[n][201][201];
        for(int i=0; i<n; i++){
            for(int[] r : memo[i]){
                Arrays.fill(r, -1);
            }
        }
        return (dfs(0, 0, 0, nums)-1+MOD)%MOD;//-1是删除全部都不选的情况
    }
    int[][][] memo;
    int dfs(int i, int j, int k, int[] nums){
        if(i == nums.length) return j == k ? 1 : 0;
        if(memo[i][j][k] != -1) return memo[i][j][k];
        long res = (long)dfs(i+1, j, k, nums) + 
                        dfs(i+1, gcd(j, nums[i]), k, nums) + 
                        dfs(i+1, j, gcd(k, nums[i]), nums);
        return memo[i][j][k] = (int)(res%MOD);
    }
    int gcd(int x, int y){
        return y==0 ? x : gcd(y, x%y);
    }
} 

递推写法

class Solution {
    int MOD = 1_000_000_007;
    public int subsequencePairCount(int[] nums) {
        int n = nums.length;
        int m = 0;
        for(int x : nums) m = Math.max(x, m);
        long[][][] f = new long[n+1][m+1][m+1];
        //f[n][i][i] = 1
        for(int i=0; i<m+1; i++){
            f[n][i][i] = 1;
        }
        for(int i=n-1; i>=0; i--){
            for(int j=0; j<m+1; j++){
                for(int k=0; k<m+1; k++){
                    f[i][j][k] = (f[i+1][j][k] + f[i+1][gcd(j, nums[i])][k] + f[i+1][j][gcd(k, nums[i])])%MOD;
                }
            }
        }
        return (int)(f[0][0][0]-1+MOD)%MOD;
    }
    int gcd(int x, int y){
        return y==0 ? x : gcd(y, x%y);
    }
} 

四,3337. 字符串转换后的长度 II

本题与T2不同,对于每个字符,它们操作后会转换成连续的nums[i]个字符,且数据范围更大,无法使用上述做法。这里我们可以使用dfs来预处理每个字符,操作 t 次后,所能形成的字符串长度。

dfs记忆化代码:

class Solution {
    private static final int MOD = 1_000_000_007;
    public int lengthAfterTransformations(String s, int t, List<Integer> nums) {
        int[] cnt = new int[26];
        for(char x : s.toCharArray()){
            cnt[x-'a']++;
        }
        long ans = 0;
        memo = new int[t+1][26];
        for(int i=0; i<t+1; i++) 
            Arrays.fill(memo[i], -1);
        int[] res = new int[26];
        for(int i=0; i<26; i++){
            res[i] = dfs(t, i, nums);
            ans += ((long)res[i] * cnt[i])%MOD;
        }
        return (int)(ans%MOD);
    }
    int[][] memo;
    int dfs(int i, int j, List<Integer> nums){
        if(i == 0) return 1;
        if(memo[i][j] != -1) return memo[i][j];
        long res = 0;
        for(int x=1; x<=nums.get(j); x++){
            res = (res + dfs(i-1, (j+x)%26, nums))%MOD;
        }
        return memo[i][j] = (int)(res%MOD);
    }
}

但是上述做法的时间复杂度为O(26*26*t),这肯定会超时间限制,将其转换成dp再来观察观察是否有其他的优化空间(对于本题至少需要一个O(logt)的时间复杂度).

递推代码:

class Solution {
    private static final int MOD = 1_000_000_007;
    public int lengthAfterTransformations(String s, int t, List<Integer> nums) {
        int[] cnt = new int[26];
        for(char x : s.toCharArray()){
            cnt[x-'a']++;
        }
        long ans = 0;
        long[][] f = new long[t+1][26];
        for(int i=0; i<26; i++)
            f[0][i] = 1;
        //预处理每个字母处理t次后,字符串的长度
        //O(26*25*t)
        for(int i=1; i<t+1; i++){
            for(int j=0; j<26; j++){
                for(int x=1; x<=nums.get(j); x++){
                    f[i][j] = (f[i][j] + f[i-1][(j+x)%26])%MOD;
                }
            }
        }
        //O(1)
        for(int k=0; k<26; k++){
            ans += (f[t][k] * cnt[k])%MOD;
        }
        return (int)(ans%MOD);
    }
}

可以发现 f[i][j] = sum(f[i-1][(j+x)%26]),拿示例一举例:

nums = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2]

根据上述公式,可以得到:

将其转换成矩阵的样式:

将 fi0~fi25 矩阵统称为 Fi,那么我们可以得到公式 Fi = M * Fi-1,不断的套用公式,我们可以得到 Fi = M * M * Fi-2 = ... = M^t * F0

这时可以发现,M是一个固定的矩阵(它是通过nums数组计算得来的),M^t 可以使用快速幂来求解,不过这里计算的是矩阵快速幂。

矩阵快速幂优化代码:

class Solution {
    private static final int MOD = 1_000_000_007;
    public int lengthAfterTransformations(String s, int t, List<Integer> nums) {
        int[] cnt = new int[26];
        for(char x : s.toCharArray()){
            cnt[x-'a']++;
        }
        int[][] m = new int[26][26];
        for(int i=0; i<26; i++){
            for(int j=1; j<=nums.get(i); j++){
                m[i][(i+j)%26]++;
            }
        }        
        int[][] f = new int[26][1];
        for(int i=0; i<26; i++)
            f[i][0] = 1;
        m = pow(m, t, f);
        long ans = 0;
        for(int i=0; i<26; i++){
            System.out.println(cnt[i] + " " + m[i][0]);
            ans = (ans + (long)cnt[i] * m[i][0])%MOD;
        }
        return (int)(ans%MOD);
    }
    int[][] pow(int[][] m, int t, int[][] f){
        int[][] res = f;
        while(t != 0){
            if((t&1)!=0){
                res = mul(m, res);
            }
            m = mul(m, m);
            t >>= 1;
        }
        return res;
    }
    //矩阵的传参一定要注意:AxB * BxC = AxC !!!
    int[][] mul(int[][] a, int[][] b){
        int[][] c = new int[a.length][b[0].length];
        //c[i][j] += a[i][k] * b[k][j];
        for(int i=0; i<a.length; i++){
            for(int k=0; k<a[0].length; k++){
                if(a[i][k] == 0) continue;
                for(int j=0; j<b[0].length; j++){
                    c[i][j] = (int)((c[i][j] + (long)a[i][k] * b[k][j])%MOD);
                }
            }
        }
        return c;
    }
}
相关推荐
88号技师3 小时前
2024年12月一区SCI-加权平均优化算法Weighted average algorithm-附Matlab免费代码
人工智能·算法·matlab·优化算法
IT猿手3 小时前
多目标应用(一):多目标麋鹿优化算法(MOEHO)求解10个工程应用,提供完整MATLAB代码
开发语言·人工智能·算法·机器学习·matlab
88号技师3 小时前
几款性能优秀的差分进化算法DE(SaDE、JADE,SHADE,LSHADE、LSHADE_SPACMA、LSHADE_EpSin)-附Matlab免费代码
开发语言·人工智能·算法·matlab·优化算法
我要学编程(ಥ_ಥ)4 小时前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
埃菲尔铁塔_CV算法4 小时前
FTT变换Matlab代码解释及应用场景
算法
许野平4 小时前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
chenziang15 小时前
leetcode hot100 合并区间
算法
chenziang15 小时前
leetcode hot100 对称二叉树
算法·leetcode·职场和发展
szuzhan.gy5 小时前
DS查找—二叉树平衡因子
数据结构·c++·算法
一只码代码的章鱼6 小时前
排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)
数据结构·算法·排序算法