20241027LeetCode421周赛
目录
题目A - 数组的最大因子得分
题目概述
给你一个整数数组 nums。
因子得分 定义为数组所有元素的最小公倍数(LCM)与最大公约数(GCD)的 乘积。
在 最多 移除一个元素的情况下,返回 nums 的 最大因子得分。
注意,单个数字的 LCM 和 GCD 都是其本身,而 空数组 的因子得分为 0。
解题思路
- 暴力枚举每一个元素作为被移除元素在此题也是可以的,因为n的大小只有100。
- 考虑优化,我们可以用一个前缀数组和一个后缀数组,去记录当前前缀或后缀的
gcd/lcm - 最后遍历前后缀数组即可,注意
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) 取余的结果。
解题思路
- 先看数据范围,
t最大为10^5且只有小写字母,所以模拟每一次变化的情况是可行的 - 用两个长度为26的长整形数组,其中一个初始化每个位置设置为1,然后按照题意逐步模拟各个元素出现的变化
- 维护一个count数组,计算源字符串中各个字母出现的频数
- 对结果求和,求和逻辑为
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) 的数量:
- 子序列
seq1和seq2不相交,意味着nums中不存在同时出现在两个序列中的下标。 seq1元素的 GCD 等于seq2元素的 GCD。
返回满足条件的子序列对的总数。
由于答案可能非常大,请返回其对 (10^9 + 7) 取余的结果。
解题思路
- 遇事不决,先观察数据量,数组长度和元素大小都在200以内,而极端情况
200 * 200 * 200 = 8 * 10^6是符合题意的 - 定义一个二维的
dp数组,dp[i][j]表示在已经选择的元素形成两个子序列的GCD分别为i和j的方案数 - 对于数组中的每个元素
num,创建一个新的动态规划状态,分为 (不加入,加入seq1,加入seq2) 三种状态 - 将
gcd从1到max(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 中的每个字符:
-
将
s[i]替换为字母表中后续的nums[s[i] - 'a']个连续字符。例如,如果s[i] = 'a'且nums[0] = 3,则字符'a'转换为后面 3 个连续字符,结果为"bcd"。 -
如果转换超过了
'z',则回绕到字母表开头。例如,如果s[i] = 'y'且nums[24] = 3,则字符'y'转换为后面 3 个连续字符,结果为"zab"。
返回 恰好 执行 t 次转换后得到的字符串的 长度。
由于答案可能非常大,返回答案对 10^9 + 7 取余的结果。
解题思路
- 先看数据范围,
t最大为10^9,那每次模拟过程变化显然是不现实了,由于是一个线性的递推关系,考虑使用矩阵快速幂优化 - 对于一个简单的子问题,有这样一个转移方程
dp[i][j] = (dp[i - 1][j + 1] + ······ + dp[i - 1][j + nums[j]]) - 初始假设每个元素出现次数为
1,初始化为一个26 * 1的矩阵prev, 考虑单次转换过程,其实是去乘一个26 * 26大小的矩阵,设矩阵为matrix - 单次的状态转移应该是:
curr = matrix * prev,最终的运算结果应该是res = matrix ^ t * prev - 最后计算每个元素在原始串中出现的次数,对各种情况求和即可,边求和边取模防止溢出
代码实现
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,常数空间
总结与思考
感觉题目还是不错的,上了大分~~~

浙公网安备 33010602011771号