Question
Given two integers
n
andk
, find how many different arrays consist of numbers from1
ton
such that there are exactlyk
inverse pairs.We define an inverse pair as following: For
ith
andjth
element in the array, ifi
<j
anda[i]
>a[j]
then it's an inverse pair; Otherwise, it's not.Since the answer may very large, the answer should be modulo 109 + 7.
Example 1:
Input: n = 3, k = 0 Output: 1 Explanation: Only the array [1,2,3] which consists of numbers from 1 to 3 has exactly 0 inverse pair.Example 2:
Input: n = 3, k = 1 Output: 2 Explanation: The array [1,3,2] and [2,1,3] have exactly 1 inverse pair.Note:
- The integer
n
is in the range [1, 1000] andk
is in the range [0, 1000].
最近专攻动态规划的练习,感觉这道题是一个很好的样本,从中学习到了很多,所以准备整理一下。
1.动态规划
首先,Inverse Pairs Array这个单词翻译到中文大概是“逆对数组”,虽然听起来有点别扭但我们就按照这个翻译好了。
在定义中,数组a中存在第i个和第j个元素,且i<j但a[i]>a[j],则是一个逆序对。而K Inverse Pairs Array 则是存在k个逆序对的数组。
在这道题的计算中,需要计算前n个数字组成存在k个逆序对的数组的不同排列数量,而这与动态规划息息相关。
当我们添加第n个数字的时候,其目的是为了满足k个逆序对,那么就将有如下几种可能性:
当n处于最后一位,即本身的位置时,没有增加新的逆序对,那么就应该找到前(n-1)个数字时,出现k个逆序对的情况。
当n处于倒数第二位时,增加了一个逆序对,那么就应该找到前(n-1)个数字时,出现(k-1)个逆序对的情况。
…………………………
同理,当n处于第一位的时候,增加了(n-1)个逆序对,那么就应该找到前(n-1)个数字时,出现(k-(n-1))个逆序对的情况。
在这里我们通常使用一个二维数组dp[n][k]来表示具体情况。表示前n个数字组成存在k个逆序对数组的排列数量,有点像最开始学习背包问题的表达。
那么我们将得到如下公式:
dp[n][k] = dp[n-1][k]+dp[n-1][k-1]+dp[n-1][k-2]+...+dp[n-1][k+1-n+1]+dp[n-1][k-n+1]
dp[n][k+1] = dp[n-1][k+1]+dp[n-1][k]+dp[n-1][k-1]+dp[n-1][k-2]+...+dp[n-1][k+1-n+1]
上下两个公式同时相减,可以得到下式:
dp[n][k+1] = dp[n][k]+dp[n-1][k+1]-dp[n-1][k+1-n]
上下两个公式同时相减,可以得到下式:
dp[n][k+1] = dp[n][k]+dp[n-1][k+1]-dp[n-1][k+1-n]
上面的公式,基本上就是程序中编写的主要部分,但一定要注意的是,k-(n-1)可能会小于0,需要进行处理,当小于0时,dp[n-1][k+1-n]赋值为0即可。
public int kInversePairs(int n, int k) {
long[][] dp = new long[n + 1][k + 1];
int mod = 1000000007;
for (int i = 1; i <= n; i++) {
if(i==1){
dp[i][0]=1;
continue;
}else{
dp[i][0]=1;
}
for (int j = 1; j <= k; j++) {
dp[i][j] = dp[i][j-1] + dp[i-1][j];
dp[i][j] =(j>=i?(dp[i][j] - dp[i-1][j-i]):dp[i][j]);
dp[i][j] = (dp[i][j]+mod) % mod;
}
}
return (int)dp[n][k];
}
对于整数n来说,它能拥有的最多逆序对是有限的,这种极端情况会出现在所有的元素都变动最大的情况下,即降序排列的数组。在这种情况下,逆序对拥有n-1+n-2……1
根据等差数列求和公式,则拥有n*(n-1)/2种情况。
在程序中,如果循环中的j值大于了n*(n-1)/2,那么这种情况是不可能出现的。所以,我们可以用j<=i*(i-1)/2来约束循环,减少无用的计算。
与此同时,题目要求要对10的九次幂+7取余,在我的操作中实际上是用了long型数组取巧了,根据正规要求,应该对每一步要求都取余。
所以最后的代码应该如下:
public int kInversePairs(int n, int k) {
if (k > n*(n-1)/2 || k < 0) return 0;
if (k == 0 || k == n*(n-1)/2) return 1;
int[][] dp = new int[n + 1][k + 1];
int mod = 1000000007;
for (int i = 1; i <= n; i++) {
if(i==1){
dp[i][0]=1;
continue;
}else{
dp[i][0]=1;
}
for (int j = 1; j <= k && j <= i * (i - 1) / 2; j++) {
dp[i][j] = (dp[i][j-1] + dp[i-1][j])%mod;
dp[i][j] =(j>=i?(dp[i][j] +mod - dp[i-1][j-i]):dp[i][j])%mod;
dp[i][j] = (dp[i][j]+mod) % mod;
}
}
return (int)dp[n][k];
}