Leetcode 第 421 场周赛题解
Leetcode 第 421 场周赛题解
题目1:3334. 数组的最大因子得分
思路
维护前后缀的 GCD 和 LCM。
先计算不移除任何元素时的因子得分,即整个数组的前缀最大公约数和最小公倍数的乘积。
枚举移除每个元素 nums[i],计算答案的最大值。
代码
/*
* @lc app=leetcode.cn id=3334 lang=cpp
*
* [3334] 数组的最大因子得分
*/
// @lc code=start
class Solution
{
public:
long long maxScore(vector<int> &nums)
{
int n = nums.size();
// pre_gcd 存储从前往后的最大公约数,初始化为1
vector<int> pre_gcd(n);
// pre_lcm 存储从前往后的最小公倍数,初始化为1
vector<long long> pre_lcm(n);
// 初始化第一个位置的最小公倍数为 nums[0]
pre_lcm[0] = nums[0];
pre_gcd[0] = nums[0];
// 从前往后计算前缀的最大公约数和最小公倍数
for (int i = 1; i < n; i++)
{
pre_gcd[i] = gcd(pre_gcd[i - 1], nums[i]);
pre_lcm[i] = lcm(pre_lcm[i - 1], nums[i]);
}
// suf_gcd 存储从后往前的最大公约数,初始化为1
vector<int> suf_gcd(n + 1);
// suf_lcm 存储从后往前的最小公倍数,初始化为1
vector<long long> suf_lcm(n + 1);
// 初始化最后一个位置的最小公倍数为 1
suf_lcm[n] = 1;
// 从后往前计算后缀的最大公约数和最小公倍数
for (int i = n - 1; i >= 0; i--)
{
suf_gcd[i] = gcd(suf_gcd[i + 1], nums[i]);
suf_lcm[i] = lcm(suf_lcm[i + 1], nums[i]);
}
// 先计算不移除任何元素时的因子得分,即整个数组的前缀最大公约数和最小公倍数的乘积
long long ans = suf_gcd[0] * suf_lcm[0];
// 枚举移除每个元素 nums[i]
for (int i = 0; i < n; i++)
{
// 当移除 nums[0] 时,只需考虑后缀的最大公约数和最小公倍数
if (i == 0)
ans = max(ans, suf_gcd[1] * suf_lcm[1]);
// 当移除 nums[n-1] 时,只需考虑前缀的最大公约数和最小公倍数
else if (i == n - 1)
ans = max(ans, pre_gcd[n - 2] * pre_lcm[n - 2]);
// 移除其他元素时,计算前缀和后缀的最大公约数和最小公倍数的乘积
else
ans = max(ans, gcd(pre_gcd[i - 1], suf_gcd[i + 1]) * lcm(pre_lcm[i - 1], suf_lcm[i + 1]));
}
// 返回最大因子得分
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的大小。
空间复杂度:O(n),其中 n 是数组 nums 的大小。
题目2:3335. 字符串转换后的长度 I
思路
因为我们只关心字符串最后的长度,所以字符串里每个位置具体是什么字符不关键。
维护 f(i,c) 表示操作 i 次以后,字符 c 在字符串中出现了几次。根据题意,递推方程为:
- f(i,‘a’)=f(i−1,‘z’)
- f(i,‘b’)=f(i−1,‘a’)+f(i−1,‘z’)
- f(i,c)=f(i−1,c−1)
代码
/*
* @lc app=leetcode.cn id=3335 lang=cpp
*
* [3335] 字符串转换后的长度 I
*/
// @lc code=start
class Solution
{
private:
const int MOD = 1e9 + 7;
public:
int lengthAfterTransformations(string s, int t)
{
vector<int> cnt(26, 0);
for (char &c : s)
cnt[c - 'a']++;
while (t--)
{
vector<int> nxt(26, 0);
for (int i = 0; i < 26; i++)
nxt[(i + 1) % 26] = cnt[i] % MOD;
nxt[1] = (nxt[1] + cnt[25]) % MOD;
cnt = nxt;
}
long long ans = 0ll;
for (int i = 0; i < 26; i++)
ans = (ans + cnt[i]) % MOD;
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n+t∣Σ∣) ,其中 n 是字符串 s 的长度, ∣Σ∣=26 是字符集大小。
空间复杂度:O(∣Σ∣) ,其中 ∣Σ∣=26 是字符集大小。
题目3:3336. 最大公约数相等的子序列数量
思路
规定空子序列的 GCD 等于 0。
考虑从右往左选数字,讨论 nums[n−1] 选或不选:
- 不选,不把 nums[n−1] 加到任何子序列中,那么需要解决的问题为:在 nums[0] 到 nums[n−2] 中选数字,且当前两个子序列的 GCD 分别为 0,0 时,最终可以得到的合法子序列对的个数。
- 选,把 nums[n−1] 加到第一个子序列中,那么需要解决的问题为:在 nums[0] 到 nums[n−2] 中选数字,且当前两个子序列的 GCD 分别为 nums[n−1],0 时,最终可以得到的合法子序列对的个数。
- 选,把 nums[n−1] 加到第二个子序列中,那么需要解决的问题为:在 nums[0] 到 nums[n−2] 中选数字,且当前两个子序列的 GCD 分别为 0,nums[n−1] 时,最终可以得到的合法子序列对的个数。
由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。
代码
#
# @lc app=leetcode.cn id=3336 lang=python3
#
# [3336] 最大公约数相等的子序列数量
#
# @lc code=start
class Solution:
def subsequencePairCount(self, nums: List[int]) -> int:
MOD = 1_000_000_007
@cache
def dfs(i: int, gcd1: int, gcd2: int) -> int:
if i >= len(nums):
return 1 if gcd1 == gcd2 else 0
# 不选
res1 = dfs(i + 1, gcd1, gcd2)
# 把 nums[i] 加到第一个子序列中
res2 = dfs(i + 1, gcd(gcd1, nums[i]), gcd2)
# 把 nums[i] 加到第二个子序列中
res3 = dfs(i + 1, gcd1, gcd(gcd2, nums[i]))
return (res1 + res2 + res3) % MOD
return (dfs(0, 0, 0) - 1) % MOD
# @lc code=end
复杂度分析
时间复杂度:O(nU2logU),其中 n 为数组 nums 的长度,U=max(nums)。由于每个状态只会计算一次,动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。本题状态个数等于 O(nU2),单个状态的计算时间为 O(logU),所以总的时间复杂度为 O(nU2logU)。
空间复杂度:O(nU2),其中 n 为数组 nums 的长度,U=max(nums)。保存多少状态,就需要多少空间。
题目4:3337. 字符串转换后的长度 II
思路
矩阵快速幂优化 DP
题解:https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii/solutions/2967037/ju-zhen-kuai-su-mi-you-hua-dppythonjavac-cd2j/
代码
/*
* @lc app=leetcode.cn id=3337 lang=cpp
*
* [3337] 字符串转换后的长度 II
*/
// @lc code=start
class Solution
{
private:
static constexpr int MOD = 1e9 + 7;
static constexpr int SIZE = 26;
using Matrix = array<array<int, SIZE>, SIZE>;
// 返回矩阵 a 和矩阵 b 相乘的结果
Matrix mul(Matrix &a, Matrix &b)
{
Matrix c{};
for (int i = 0; i < SIZE; i++)
{
for (int k = 0; k < SIZE; k++)
{
if (a[i][k] == 0)
continue;
for (int j = 0; j < SIZE; j++)
c[i][j] = (c[i][j] + (long long)a[i][k] * b[k][j]) % MOD;
}
}
return c;
}
// 返回 n 个矩阵 a 相乘的结果
Matrix pow(Matrix a, int n)
{
Matrix res = {};
for (int i = 0; i < SIZE; i++)
res[i][i] = 1; // 单位矩阵
while (n)
{
if (n & 1)
res = mul(res, a);
a = mul(a, a);
n >>= 1;
}
return res;
}
public:
int lengthAfterTransformations(string s, int t, vector<int> &nums)
{
Matrix m{};
for (int i = 0; i < SIZE; i++)
for (int j = i + 1; j <= i + nums[i]; j++)
m[i][j % SIZE] = 1;
m = pow(m, t);
int cnt[SIZE]{};
for (char &c : s)
cnt[c - 'a']++;
long long ans = 0;
for (int i = 0; i < SIZE; i++)
{
// m 第 i 行的和就是 f[t][i]
ans += reduce(m[i].begin(), m[i].end(), 0LL) * cnt[i];
}
return ans % MOD;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n+∣Σ∣3logt),其中 n 是字符串 s 的长度,∣Σ∣=26 是字符集合的大小。
空间复杂度:O(∣Σ∣2),其中 ∣Σ∣=26 是字符集合的大小。