20241027LeetCode421周赛

目录


题目A - 数组的最大因子得分

题目概述

给你一个整数数组 nums。

因子得分 定义为数组所有元素的最小公倍数(LCM)与最大公约数(GCD)的 乘积。

在 最多 移除一个元素的情况下,返回 nums 的 最大因子得分。

注意,单个数字的 LCM 和 GCD 都是其本身,而 空数组 的因子得分为 0。

解题思路

  1. 暴力枚举每一个元素作为被移除元素在此题也是可以的,因为n的大小只有100。
  2. 考虑优化,我们可以用一个前缀数组和一个后缀数组,去记录当前前缀或后缀的gcd/lcm
  3. 最后遍历前后缀数组即可,注意gcd(a, b, c) = gcd(gcd(a, b), c)lcm(a, b, c) = lcm(lcm(a, b), c)

代码实现

public long maxScore(int[] nums) {
    int n = nums.length;
    if (n == 1) return 1l * nums[0] * nums[0];
    long[] preGCD = new long[n], sufGCD = new long[n];
    long[] preLCM = new long[n], sufLCM = new long[n];
    preGCD[0] = nums[0];
    preLCM[0] = nums[0];
    for (int i = 1; i < n; i++) {
        preGCD[i] = gcd(preGCD[i - 1], nums[i]);
        preLCM[i] = lcm(preLCM[i - 1], nums[i]);
    }

    sufGCD[n - 1] = nums[n - 1];
    sufLCM[n - 1] = nums[n - 1];
    for (int i = n - 2; i >= 0; i--) {
        sufGCD[i] = gcd(sufGCD[i + 1], nums[i]);
        sufLCM[i] = lcm(sufLCM[i + 1], nums[i]);
    }

    long ans = sufGCD[0] * sufLCM[0];
    for (int i = 0; i < n; i++) {
        long curGCD, curLCM;
        if (i == 0) {
            curGCD = sufGCD[1];
            curLCM = sufLCM[1];
        } else if (i == n - 1) {
            curGCD = preGCD[n - 2];
            curLCM = preLCM[n - 2];
        } else {
            curGCD = gcd(preGCD[i - 1], sufGCD[i + 1]);
            curLCM = lcm(preLCM[i - 1], sufLCM[i + 1]);
        }
        ans = Math.max(ans, curGCD * curLCM);
    }
    return ans;
}

private static long gcd(long a, long b) {
    return b == 0 ? a : gcd(b, a % b);
}

private static long lcm(long a, long b) {
    return (a / gcd(a, b)) * b;
}

复杂度分析

  • 时间复杂度O(n),处理前后缀数组O(n),遍历数组O(n)
  • 空间复杂度O(n),用于存储前后缀数组

题目B - 字符串转换后的长度Ⅰ

题目概述

给你一个字符串 S 和一个整数 t,表示要执行的 转换 次数。每次转换需要根据以下规则替换字符串 S 中的每个字符:

  • 如果字符是 'z',则将其替换为字符串 "ab"
  • 否则,将其替换为字母表中的下一个字符。例如,'a' 替换为 'b''b' 替换为 'c',依此类推。

返回恰好执行 t 次转换后得到的字符串的长度。

由于答案可能非常大,返回其对 (10^9 + 7) 取余的结果。

解题思路

  1. 先看数据范围,t最大为10^5且只有小写字母,所以模拟每一次变化的情况是可行的
  2. 用两个长度为26的长整形数组,其中一个初始化每个位置设置为1,然后按照题意逐步模拟各个元素出现的变化
  3. 维护一个count数组,计算源字符串中各个字母出现的频数
  4. 对结果求和,求和逻辑为count[i] * dp[i],边求和边取模防止溢出

代码实现

public int lengthAfterTransformations(String s, int t) {
    int n = s.length(), MOD = 1000000007;
    long[] dp1 = new long[26], dp2 = new long[26];
    Arrays.fill(dp1, 1);

    for (int step = 1; step <= t; step++) {
        for (int c = 0; c < 26; c++) {
            if (c < 25) {
                dp2[c] = dp1[c + 1];
            } else {
                dp2[c] = (dp1[0] + dp1[1]) % MOD;
            }
        }
        for (int i = 0; i < 26; i++) {
            dp1[i] = dp2[i];
        }
    }

    int[] count = new int[26];
    for (char c : s.toCharArray()) {
        count[c - 'a']++;
    }
    long ans = 0;
    for (int c = 0; c < 26; c++) {
        ans = (ans + dp1[c] * count[c]) % MOD;
    }
    return (int) ans;
}

复杂度分析

  • 时间复杂度O(t + n),严格来说是确切的运算量是2 * 26 * t + n = 52t + n
  • 空间复杂度O(1),使用的都是常数空间

题目C - 最大公约数相等的子序列数量

题目概述

给你一个整数数组 nums

请你统计所有满足一下条件的非空 子序列(seq1, seq2) 的数量:

  • 子序列 seq1seq2 不相交,意味着 nums 中不存在同时出现在两个序列中的下标。
  • seq1 元素的 GCD 等于 seq2 元素的 GCD。

返回满足条件的子序列对的总数。

由于答案可能非常大,请返回其对 (10^9 + 7) 取余的结果。

解题思路

  1. 遇事不决,先观察数据量,数组长度和元素大小都在200以内,而极端情况200 * 200 * 200 = 8 * 10^6是符合题意的
  2. 定义一个二维的dp数组,dp[i][j]表示在已经选择的元素形成两个子序列的GCD分别为ij的方案数
  3. 对于数组中的每个元素num,创建一个新的动态规划状态,分为 (不加入,加入seq1,加入seq2) 三种状态
  4. gcd1max(nums)的情况累加即可

代码实现

public static int subsequencePairCount(int[] nums) {
    int n = nums.length, maxNum = Integer.MIN_VALUE;
    int MOD = 1000000007;
    for (int i = 0; i < n; i++) maxNum = Math.max(maxNum, nums[i]);
    int[][] dp = new int[maxNum + 1][maxNum + 1];
    dp[0][0] = 1;

    for (int num : nums) {
        int[][] temp = new int[maxNum + 1][maxNum + 1];
        for (int i = 0; i <= maxNum; i++) {
            for (int j = 0; j <= maxNum; j++) {
                int prev = dp[i][j];
                if (prev == 0) continue;
                temp[i][j] = (temp[i][j] + prev) % MOD;
                int i1 = i == 0 ? num : gcd(num, i);
                temp[i1][j] = (temp[i1][j] + prev) % MOD;
                int j1 = j == 0 ? num : gcd(num, j);
                temp[i][j1] = (temp[i][j1] + prev) % MOD;
            }
        }
        dp = temp;
    }

    int ans = 0;
    for (int i = 1; i <= maxNum; i++) {
        ans = (ans + dp[i][i]) % MOD;
    }
    return ans;
}

private static int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

复杂度分析

  • 时间复杂度O(n * k ^ 2), 其中k为数组最大值,主要时间复杂度在处理dp状态
  • 空间复杂度O(k ^ 2), 用于进行dp状态转移

题目D - 字符串转换后的长度Ⅱ

题目概述

给你一个由小写英文字母组成的字符串 s,一个整数 t 表示要执行的 转换 次数,以及一个长度为 26 的数组 nums。每次 转换 需要根据以下规则对字符串 s 中的每个字符:

  1. s[i] 替换为字母表中后续的 nums[s[i] - 'a'] 个连续字符。例如,如果 s[i] = 'a'nums[0] = 3,则字符 'a' 转换为后面 3 个连续字符,结果为 "bcd"

  2. 如果转换超过了 'z',则回绕到字母表开头。例如,如果 s[i] = 'y'nums[24] = 3,则字符 'y' 转换为后面 3 个连续字符,结果为 "zab"

返回 恰好 执行 t 次转换后得到的字符串的 长度

由于答案可能非常大,返回答案对 10^9 + 7 取余的结果。

解题思路

  1. 先看数据范围,t最大为10^9,那每次模拟过程变化显然是不现实了,由于是一个线性的递推关系,考虑使用矩阵快速幂优化
  2. 对于一个简单的子问题,有这样一个转移方程dp[i][j] = (dp[i - 1][j + 1] + ······ + dp[i - 1][j + nums[j]])
  3. 初始假设每个元素出现次数为1,初始化为一个26 * 1的矩阵prev, 考虑单次转换过程,其实是去乘一个26 * 26大小的矩阵,设矩阵为matrix
  4. 单次的状态转移应该是: curr = matrix * prev,最终的运算结果应该是res = matrix ^ t * prev
  5. 最后计算每个元素在原始串中出现的次数,对各种情况求和即可,边求和边取模防止溢出

代码实现

public static int lengthAfterTransformations(String s, int t, List<Integer> nums) {
    int MOD = 1000000007, n = 26;
    long[][] matrix = new long[n][n];
    for (int i = 0; i < n; i++) {
        int len = nums.get(i);
        for (int j = 0; j < len; j++) {
            int k = (j + 1 + i) % n;
            matrix[k][i] = (matrix[k][i] + 1) % MOD;
        }
    }

    long[][] quickPowMatrix = matrixPower(matrix, t, MOD);
    long[] cnt1 = new long[n], cnt2 = new long[n];
    for (int i = 0; i < s.length(); i++) cnt1[s.charAt(i) - 'a']++;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cnt2[i] = (cnt2[i] + quickPowMatrix[i][j] * cnt1[j]) % MOD;
        }
    }

    long ans = 0;
    for (int i = 0; i < n; i++) ans = (ans + cnt2[i]) % MOD;
    return (int) ans;
}

private static long[][] matrixPower(long[][] matrix, int t, int mod) {
    int n = matrix.length;
    long[][] ans = new long[n][n];
    for (int i = 0; i < n; i++) {
        ans[i][i] = 1;
    }
    while (t > 0) {
        if ((t & 1) == 1) {
            ans = multiplyMatrix(ans, matrix, mod);
        }
        matrix = multiplyMatrix(matrix, matrix, mod);
        t >>= 1;
    }
    return ans;
}

private static long[][] multiplyMatrix(long[][] a, long[][] b, int mod) {
    int n = a.length;
    long[][] ans = new long[n][n];
    for (int i = 0; i < n; i++) {
        for (int k = 0; k < n; k++) {
            if (a[i][k] == 0) continue;
            for (int j = 0; j < n; j++) {
                if (b[k][j] == 0) continue;
                ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % mod;
            }
        }
    }
    return ans;
}

复杂度分析

  • 时间复杂度O(n + t * 26 ^ 3), 遍历数组O(n), 处理矩阵O(t * 26 ^ 3)
  • 空间复杂度O(1), 与字符集相关,该题为26,常数空间

总结与思考

感觉题目还是不错的,上了大分~~~

posted @ 2024-10-30 01:32  anyj1024  阅读(41)  评论(0)    收藏  举报