LeetCode629.K个逆序对数组(dp)

LeetCode629.K个逆序数组(dp)

题目传送门

给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。
逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。
由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。

解析

本题主要参考的官方题解,但是官方题解有些地方当时理解的时候有些困难,在这里再记录一下,便于理解。
官方题解
f[i][j]表示长度为i的数组,恰好包含j个逆序对的方案数,第i个元素的所有可能取值为1~i中的一个数字,我们假设第i个元素为k,那么数组中逆序对的个数为一下两部分之和:
1.数字k与另外i-1个元素产生的逆序对的个数
2.另外i-1个元素内部产生的逆序对的个数

对第一部分:数字k放在最后一位,数组中有i-k个比数字k大的数,所以k贡献的逆序对个数为i-k
对第二部分:因为f[i][j]表示的有j个逆序对的情况,所以我们希望第二部分的逆序对个数为j-(i-k),这里逆序对的个数只与元素的相对大小有关,不包含k的数组元素为1,…,k-1和k+1,…i。我们可以把后半部分整体减一,此时逆序对的个数不变,我们的目标变成了1,…i-1,希望它有j-(i-k)个逆序对,由此我们可以得到状态转移方程。
在这里插入图片描述
边界条件为:
f[0][0]=1,不用任何数字构成一个空数组,包含0个逆序对
f[i][j<0]=0,逆序对的数量一定是非负整数
最终的答案为f[n][k]

优化

这里的优化非常巧妙,需要着重理解
首先上述动态规划的状态数量为O(nk),而求出每一个f[i][j]需要经过O(n)的时间复杂度,总时间复杂度会超时
注意f[[i][j-1]和f[i][j]的状态转移方程:
在这里插入图片描述
简单展开
f[i][j]=f[i-1][j]+f[i-1][j-1]+…+f[i-1][j-i+1]
f[i][j-1]=f[i-1][j-1]+f[i-1][j-2]+…+f[i-1][j-i]
不难看出
f[i][j]=f[i][j-1]+f[i-1][j]-f[i-1][j-i]
这样我们就可以在常数时间内求出f[i][j]

此外,我们可以发现,对f[i][j]的求解只会用到f[i][…]和f[i-1][…],因此我们可以再对空间进行优化,用两个一维数组交替进行状态转移,此步对应代码中now=i&1,prev=now^1,now表示当前要求的数组,prev则是前一个数组,now随着i增大是1,0交替变化,prev则是随着now的变化0,1交替变化,最开始now为1,此时prev为0,当i增加时,now变为0,prev变为1,此时dp[prev]的值即是上一个now的值,由此实现两数组的交替状态转移。

代码

class Solution
{
public:
    int kInversePairs(int n, int k)
    {
        int mod = 1e9 + 7;
        int dp[2][1010];
        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++)
        {
            int now = i & 1, prev = now ^ 1;
            for (int j = 0; j <= k; j++)
            {
                dp[now][j] = (j - 1 >= 0 ? dp[now][j - 1] : 0) - (j - i >= 0 ? dp[prev][j - i] : 0) + dp[prev][j];
                if (dp[now][j] >= mod)
                    dp[now][j] -= mod;
                else if (dp[now][j] < 0)
                    dp[now][j] += mod;
            }
        }
        return dp[n & 1][k];
    }
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值