目录
K个逆序对数组
描述
给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。
逆序对的定义如下:对于数组的第 i 个和第 j 个元素,如果满 i < j 且 a[i] > a[j],则其为一个逆序对;否则不是。
由于答案可能很大,只需要返回 答案 mod 的值。
示例 1
输入: n = 3, k = 0 输出: 1 解释: 只有数组 [1,2,3] 包含了从1到3的整数并且正好拥有 0 个逆序对。
示例 2
输入: n = 3, k = 1 输出: 2 解释: 数组 [1,3,2] 和 [2,1,3] 都有 1 个逆序对。
说明
n 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。
方法:动态规划
假设 f[i][j] 表示使用 [1,i] 的元素构成长度为i,恰好包含j个逆序对数组的个数。
假设我们第 i 个元素(即数组的最后一位)选取了数字 k,那么数组的前i-1个位置由 1,2,...,k-1 以及 k+1,k+2,...,i 的某个排列组成。数组中逆序对的个数可以看成以下两个部分之和:
- 数字k与前 i-1 个元素产生的逆序对个数
- 前 i-1 个元素内部产生的逆序对个数
对于第一部分,我们可以求出数字k会贡献 i-k 个逆序对(即和元素k+1,k+2,...,i产生逆序对)。
对于第二部分,我们希望他能有 j-(i-k) 个逆序对,这样才能一共有j个逆序对。
由于我们关心的是前 i-1 个元素内部产生的逆序对个数,而逆序对个数只跟元素的相对大小有关,所以我们可以:
- 1,2,...,k-1这些元素保持不变
- k+1,k+2,...,i这些元素-1,变为k,k+1,...,i-1
所以我们的目标变为了求使用 [1,i-1] 的元素构成长度为i-1,恰好包含 j-(i-k) 个逆序对数组的个数。也就对应着我们定义的f[i-1][j-(i-k)]。
所以我们可以得到状态转移方程:
其中f[0][0]=1,最终返回f[n][k]的值即可。
上述动态规划的状态数量为 O(nk),而求出每一个 f[i][j] 需要 O(n) 的时间复杂度,因此总时间复杂度为 O(n^2k),会超出时间限制,我们必须要进行优化。
我们考虑 f[i][j-1] 和 f[i][j] 的状态转移方程:
可以得到从 f[i][j-1] 到 f[i][j] 的递推式:
这样我们就可以在 O(1) 的时间计算出每个 f[i][j],总时间复杂度降低为 O(nk)。
此外,由于 f[i][j] 只会从第 f[i-1][..] 和 f[i][..] 转移而来,因此我们可以对动态规划使用的空间进行优化,即使用两个一维数组交替地进行状态转移,空间复杂度从 O(nk) 降低为 O(k)。
class Solution {
public int kInversePairs(int n, int k) {
final int MOD = 1000000007;
int[][] f = new int[2][k + 1];
f[0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= k; ++j) {
int cur = i & 1, prev = cur ^ 1;
f[cur][j] = (j - 1 >= 0 ? f[cur][j - 1] : 0) - (j - i >= 0 ? f[prev][j - i] : 0) + f[prev][j];
if (f[cur][j] >= MOD) {
f[cur][j] -= MOD;
} else if (f[cur][j] < 0) {
f[cur][j] += MOD;
}
}
}
return f[n & 1][k];
}
}