1. 题目来源
很不错的题解:
2. 题目解析
首先 Q3 题应该要能做出来的。但这也需要对题目有一个完整的分析和理解。
状态定义:f[i][j]
已经填了 i
个数,且 arr1[i] = j 的方案数。
状态转移:
- 已知 arr1[i]=j,那么 arr2[i] = nums[i] - j
- f[i][j] 的转移肯定与上一个数有关。这里有限制如下:
- arr1[i-1]<=j
- arr2[i-1]>=nums[i]-j
- 且,arr2[i-1]=nums[i-1]-arr1[i-1],即有:
- nums[i-1]-arr[i-1]>=nums[i]-j
- arr1[i-1] <= j
- 此时,对于状态转移来讲,我们需要枚举 arr1[i-1] 的取值,假设为 k,则有:
- nums[i-1]-k>=nums[i]-j
- k<=j
- 这里有点特殊的是,需要先枚举 i(位置)、再枚举 j(i 位置的取值),再枚举 k(状态转移的取值)。中间夹杂一些判断即可。
结果返回:
- 最终的 f[n-1][j] 应该将 j = 0~nums[n-1] 这些答案全部累加起来。因为这些都是符合定义下的答案。
关于 T4 来看,需要优化掉内层的循环。f[i][j]
是由一系列 f[i-1][k] 得到的,且经过不等式的移项就可以发现:
- 0≤k≤nums[i−1]−nums[i]+j
- 说明 k 是一段连续的前缀
- 则可以使用前缀和优化 dp 的思想,对dp 进行优化。
- 且 f[i] 仅与 f[i-1] 有关,则可以使用滚动数组进行优化。
- 时间复杂度: O ( n ∗ m 2 ) O(n*m^2) O(n∗m2) 、 O ( n ∗ m ) O(n*m) O(n∗m)
- 空间复杂度: O ( n m ) O(nm) O(nm)
class Solution {
public:
int countOfPairs(vector<int>& nums) {
int n = nums.size();
const int MOD = 1e9+7;
int f[n][51];
memset(f, 0, sizeof f);
for (int i = 0; i <= nums[0]; i ++ ) f[0][i] = 1;
for (int i = 1; i < n; i ++ ) {
for (int j = 0; j <= nums[i]; j ++ ) {
int t = nums[i] - j; // 不等式条件
for (int k = 0; k <= j; k ++ ) { // arr[i-1] 的取值范围
if (nums[i - 1] - k >= t) {
f[i][j] = (f[i][j] + f[i - 1][k]) % MOD; // 取模放到括号外面哈....
}
}
}
}
int res = 0;
for (int i = 0; i <= nums[n - 1]; i ++ ) res = (res + f[n - 1][i]) % MOD;
return res;
}
};
T4:
class Solution {
public:
int countOfPairs(vector<int>& nums) {
int n = nums.size();
int mx = 0;
for (int x : nums) mx = max(mx, x);
const int MOD = 1e9 + 7;
// 初始化 f[0][j] 以及对应的前缀和
long long f[n][mx + 1], g[n][mx + 1];
memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g));
for (int i = 0; i <= nums[0]; i++) f[0][i] = 1;
g[0][0] = f[0][0];
for (int i = 1; i <= mx; i++) g[0][i] = (g[0][i - 1] + f[0][i]) % MOD;
for (int i = 1; i < n; i++) {
// 计算单个 DP 状态
for (int j = 0; j <= nums[i]; j++) {
int lim = min(j, j + nums[i - 1] - nums[i]);
if (lim >= 0) f[i][j] = g[i - 1][lim];
}
// 计算前缀和
g[i][0] = f[i][0];
for (int j = 1; j <= mx; j++) g[i][j] = (g[i][j - 1] + f[i][j]) % MOD;
}
return g[n - 1][mx];
}
};