问题背景
给你一个长度为 n n n 的 正 整数数组 n u m s nums nums。
如果两个 非负 整数数组 ( a r r 1 , a r r 2 ) (arr_1, arr_2) (arr1,arr2) 满足以下条件,我们称它们是 单调 数组对:
两个数组的长度都是 n n n。
- a r r 1 arr_1 arr1 是单调 非递减 的,换句话说 a r r 1 0 ≤ a r r 1 1 ≤ . . . ≤ a r r 1 n − 1 arr_10 \le arr_11 \le ... \le arr_1n - 1 arr10≤arr11≤...≤arr1n−1。
- a r r 2 arr_2 arr2 是单调 非递增 的,换句话说 a r r 2 0 ≤ a r r 2 1 ≤ . . . ≤ a r r 2 n − 1 arr_20 \le arr_21 \le ... \le arr_2n - 1 arr20≤arr21≤...≤arr2n−1。
- 对于所有的 0 ≤ i ≤ n − 1 0 \le i \le n - 1 0≤i≤n−1 都有 a r r 1 i + a r r 2 i = = n u m s i arr_1i + arr_2i == numsi arr1i+arr2i==numsi。
请你返回所有 单调 数组对的数目。
由于答案可能很大,请你将它对 1 0 9 + 7 10 ^ 9 + 7 109+7 取余 后返回。
数据约束
- 1 ≤ n = n u m s . l e n g t h ≤ 2000 1 \le n = nums.length \le 2000 1≤n=nums.length≤2000
- 1 ≤ n u m s i ≤ 50 1 \le numsi \le 50 1≤numsi≤50
解题过程
周赛三四题的水准,两道题目只在数据规模上有差异,目前只能尝试写灵神题解的解释。
这题虽然对两个数组有要求,但是实际上只需要枚举其中一个数组的情况,把对另外一个数组中元素的要求当成约束就行。
根据动态规划缩小问题规模的思想:
- 原问题是下标 0 0 0 到 n − 1 n - 1 n−1中的单调数组对的个数,且 a r r 1 n − 1 = j = 0 , 1 , 2 , . . . , n u m s n − 1 arr_1n−1 = j = 0, 1, 2, ..., numsn - 1 arr1n−1=j=0,1,2,...,numsn−1。
- 子问题是下标 0 0 0 到 i i i 中的单调数组对的个数,且 a r r 1 i = j arr_1i = j arr1i=j,将其记作 d p i j dpij dpij。
用 k k k表示 a r r 1 i − 1 arr_1i−1 arr1i−1,那么根据约束条件: { a r r 1 i − 1 ≤ a r r 1 i a r r 2 i − 1 ≥ a r r 2 i \ \begin{cases} arr_1i - 1 \le arr_1i \\ arr_2i - 1 \ge arr_2i \\ \end{cases} {arr1i−1≤arr1iarr2i−1≥arr2i,即 { k ≤ j n u m s i − 1 − k ≥ n u m s i − j \ \begin{cases} k \le j \\ numsi - 1 - k \ge numsi - j \\ \end{cases} {k≤jnumsi−1−k≥numsi−j,可以得到 k k k 的上界。
解得 k ≤ m i n ( j , n u m s i − 1 − n u m s i + j ) = j + m i n ( n u m s i − 1 − n u m s i , 0 ) k \le min(j, numsi - 1 - numsi + j) = j + min(numsi - 1 - numsi, 0) k≤min(j,numsi−1−numsi+j)=j+min(numsi−1−numsi,0),由于所有数组中的元素都是非负的,而 n u m s i = a r r 1 i + a r r 2 i numsi = arr_1i + arr_2i numsi=arr1i+arr2i,所以 k ≤ n u m s i − 1 k \le numsi - 1 k≤numsi−1。
记 m a x K = j + m i n ( n u m s i − 1 − n u m s i , 0 ) maxK = j + min(numsi - 1 - numsi, 0) maxK=j+min(numsi−1−numsi,0),那么 d p i j = { 0 , m a x K < 0 Σ k = 0 m a x K d p i − 1 k , m a x K ≥ 0 dpij = \begin{cases} 0,& maxK \lt 0 \\ \mathop{\Sigma} \limits _ {k = 0} ^ {maxK} dpi - 1k,& maxK \ge 0 \\ \end{cases} dpij=⎩ ⎨ ⎧0,k=0ΣmaxKdpi−1k,maxK<0maxK≥0。
这里 Σ k = 0 m a x K d p i − 1 k \mathop{\Sigma} \limits _ {k = 0} ^ {maxK} dpi - 1k k=0ΣmaxKdpi−1k 表示对所有的 k k k 从 0 0 0 到 m a x K maxK maxK 的情况,求下标 0 0 0 到 i − 1 i - 1 i−1 中的单调数组对的个数之和,要求 a r r 1 = k arr_1 = k arr1=k。这显然满足前缀和的定义,记 s j = Σ k = 0 j d p i − 1 k sj = \mathop{\Sigma} \limits _ {k = 0} ^ {j} dpi - 1k sj=k=0Σjdpi−1k,那么上述状态转移方程 ( 3 ) (3) (3) 可以简化为 d p i j = { 0 , m a x K < 0 s m a x K , m a x K ≥ 0 dpij = \begin{cases} 0,& maxK \lt 0 \\ smaxK,& maxK \ge 0 \\ \end{cases} dpij={0,smaxK,maxK<0maxK≥0。
初始值 d p 0 j = 1 dp0j = 1 dp0j=1,其中 j = 0 , 1 , 2 , . . . , n u m s 0 j = 0, 1, 2, ..., nums0 j=0,1,2,...,nums0。这表示的是,下标 0 0 0 到 0 0 0 中的单调数组对的个数,也就是只考虑数组中第一个元素的情况, a r r 1 i arr_1i arr1i 可以是合法范围内任意值。
后续还有进一步优化,目前一下子掌握不了,先暂时放弃。
具体实现
java
class Solution {
// 根据题意定义模数
private static final int MOD = 1000000007;
public int countOfPairs(int[] nums) {
int n = nums.length;
// m 表示 nums 数组中的最大值,所有数组中的元素都不会超过这个范围,也就是应枚举的最大值
int m = Arrays.stream(nums).max().getAsInt();
long [][] dp = new long[n][m + 1];
long[] preSum = new long[m + 1];
// 填充初始值,只考虑数组中第一个元素的情况
Arrays.fill(dp[0], 0, nums[0] + 1, 1);
for(int i = 1; i < n; i++) {
// 计算前缀和,这是它的定义
preSum[0] = dp[i - 1][0];
for(int k = 1; k <= m; k++) {
preSum[k] = (preSum[k - 1] + dp[i - 1][k]) % MOD;
}
for(int j = 0; j <= nums[i]; j++) {
int maxK = j + Math.min(nums[i - 1] - nums[i], 0);
dp[i][j] = maxK >= 0 ? preSum[maxK] % MOD : 0;
}
}
// 答案是考虑完数组中最后一个元素之后,对所有可能情形求和
return (int) (Arrays.stream(dp[n - 1], 0, nums[n - 1] + 1).sum() % MOD);
}
}