Hard
题目描述
给定一个整数数组 nums。
你的任务是找到满足以下条件的非空子序列对 (seq1, seq2) 的数量:
- 子序列
seq1和seq2是不相交的,即它们之间没有共同的nums索引。 seq1元素的最大公约数等于seq2元素的最大公约数。
返回此类对的总数。
由于答案可能非常大,请返回对 10^9 + 7 取模的结果。
示例 1:
输入:nums = [1,2,3,4]
输出:10
示例 2:
输入:nums = [10,20,30]
输出:2
示例 3:
输入:nums = [1,1,1,1]
输出:50
约束条件:
1 <= nums.length <= 2001 <= nums[i] <= 200
提示:
- 使用动态规划存储到索引
i为止,GCD 为g1和g2的子序列数量。
解题思路
这道题目需要我们找到两个不相交的子序列,使得它们的最大公约数相等。
核心思路:
动态规划状态设计:使用三维DP,
dp[i][g1][g2]表示考虑前i个元素时,第一个子序列GCD为g1,第二个子序列GCD为g2的方案数。状态转移:对于每个元素
nums[i],有三种选择:- 不选择该元素:
dp[i+1][g1][g2] += dp[i][g1][g2] - 加入第一个子序列:
dp[i+1][gcd(g1, nums[i])][g2] += dp[i][g1][g2] - 加入第二个子序列:
dp[i+1][g1][gcd(g2, nums[i])] += dp[i][g1][g2]
- 不选择该元素:
初始状态:
dp[0][0][0] = 1,表示没有选择任何元素时有一种方案(空方案)。答案统计:遍历所有可能的GCD值
g,累加dp[n][g][g]的值。优化技巧:
- 由于数组元素最大为200,所以GCD最大也是200
- 使用滚动数组可以优化空间复杂度
- 预计算GCD值避免重复计算
这个方法的时间复杂度是 O(n × max_val²),空间复杂度是 O(max_val²),在给定约束下完全可行。
代码实现
class Solution {
public:
int subsequencePairCount(vector<int>& nums) {
const int MOD = 1e9 + 7;
int n = nums.size();
int maxVal = 200;
// dp[g1][g2] = number of ways with gcd g1 and g2
vector<vector<long long>> dp(maxVal + 1, vector<long long>(maxVal + 1, 0));
vector<vector<long long>> newDp(maxVal + 1, vector<long long>(maxVal + 1, 0));
dp[0][0] = 1; // empty subsequences
for (int num : nums) {
// Reset newDp
for (int i = 0; i <= maxVal; i++) {
for (int j = 0; j <= maxVal; j++) {
newDp[i][j] = dp[i][j]; // don't take current number
}
}
for (int g1 = 0; g1 <= maxVal; g1++) {
for (int g2 = 0; g2 <= maxVal; g2++) {
if (dp[g1][g2] == 0) continue;
// Add to first subsequence
int newG1 = (g1 == 0) ? num : __gcd(g1, num);
newDp[newG1][g2] = (newDp[newG1][g2] + dp[g1][g2]) % MOD;
// Add to second subsequence
int newG2 = (g2 == 0) ? num : __gcd(g2, num);
newDp[g1][newG2] = (newDp[g1][newG2] + dp[g1][g2]) % MOD;
}
}
dp = newDp;
}
long long result = 0;
for (int g = 1; g <= maxVal; g++) {
result = (result + dp[g][g]) % MOD;
}
return result;
}
};
class Solution:
def subsequencePairCount(self, nums: List[int]) -> int:
MOD = 10**9 + 7
max_val = 200
# dp[g1][g2] = number of ways with gcd g1 and g2
dp = [[0] * (max_val + 1) for _ in range(max_val + 1)]
dp[0][0] = 1 # empty subsequences
def gcd(a, b):
while b:
a, b = b, a % b
return a
for num in nums:
new_dp = [row[:] for row in dp] # copy current state
for g1 in range(max_val + 1):
for g2 in range(max_val + 1):
if dp[g1][g2] == 0:
continue
# Add to first subsequence
new_g1 = num if g1 == 0 else gcd(g1, num)
new_dp[new_g1][g2] = (new_dp[new_g1][g2] + dp[g1][g2]) % MOD
# Add to second subsequence
new_g2 = num if g2 == 0 else gcd(g2, num)
new_dp[g1][new_g2] = (new_dp[g1][new_g2] + dp[g1][g2]) % MOD
dp = new_dp
result = 0
for g in range(1, max_val + 1):
result = (result + dp[g][g]) % MOD
return result
public class Solution {
public int SubsequencePairCount(int[] nums) {
const int MOD = 1000000007;
int maxVal = 200;
// dp[g1][g2] = number of ways with gcd g1 and g2
long[,] dp = new long[maxVal + 1, maxVal + 1];
long[,] newDp = new long[maxVal + 1, maxVal + 1];
dp[0, 0] = 1; // empty subsequences
foreach (int num in nums) {
// Copy current state
for (int i = 0; i <= maxVal; i++) {
for (int j = 0; j <= maxVal; j++) {
newDp[i, j] = dp[i, j];
}
}
for (int g1 = 0; g1 <= maxVal; g1++) {
for (int g2 = 0; g2 <= maxVal; g2++) {
if (dp[g1, g2] == 0) continue;
// Add to first subsequence
int newG1 = (g1 == 0) ? num : GCD(g1, num);
newDp[newG1, g2] = (newDp[newG1, g2] + dp[g1, g2]) % MOD;
// Add to second subsequence
int newG2 = (g2 == 0) ? num : GCD(g2, num);
newDp[g1, newG2] = (newDp[g1, newG2] + dp[g1, g2]) % MOD;
}
}
dp = newDp;
newDp = new long[maxVal + 1, maxVal + 1];
}
long result = 0;
for (int g = 1; g <= maxVal; g++) {
result = (result + dp[g, g]) % MOD;
}
return (int)result;
}
private int GCD(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
var subsequencePairCount = function(nums) {
const MOD = 1000000007;
const n = nums.length;
function gcd(a, b) {
return b === 0 ? a : gcd(b, a % b);
}
// dp[mask][g] = number of ways to choose subsequence represented by mask with GCD g
const dp = new Map();
// Generate all possible subsequences and their GCDs
for (let mask = 1; mask < (1 << n); mask++) {
let g = 0;
for (let i = 0; i < n; i++) {
if (mask & (1 << i)) {
g = g === 0 ? nums[i] : gcd(g, nums[i]);
}
}
if (!dp.has(g)) dp.set(g, new Map());
if (!dp.get(g).has(mask)) dp.get(g).set(mask, 0);
dp.get(g).set(mask, dp.get(g).get(mask) + 1);
}
let result = 0;
// For each possible GCD value
for (let [g, masks] of dp) {
const maskList = Array.from(masks.keys());
// Count pairs of disjoint subsequences with same GCD
for (let i = 0; i < maskList.length; i++) {
for (let j = i; j < maskList.length; j++) {
const mask1 = maskList[i];
const mask2 = maskList[j];
// Check if masks are disjoint
if ((mask1 & mask2) === 0) {
if (i === j) {
result = (result + 1) % MOD;
} else {
result = (result + 2) % MOD;
}
}
}
}
}
return result;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n × V²) 其中 n 是数组长度,V 是数组中的最大值(200)。对于每个元素,需要遍历所有可能的 GCD 对 |
| 空间复杂度 | O(V²) 存储动态规划状态,需要一个二维数组来记录所有可能的 GCD 组合 |