• Leetcode 629.K个逆序对数组


    K个逆序对数组

    给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。

    逆序对的定义如下:对于数组的第i个和第 j个元素,如果满i < j且 a[i] > a[j],则其为一个逆序对;否则不是。

    由于答案可能很大,只需要返回 答案 mod 109 + 7 的值。

    示例 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 个逆序对。

    说明:

    1.  n 的范围是 [1, 1000] 并且 k 的范围是 [0, 1000]。

    思路

    这道题给了我们1到n总共n个数字,让我们任意排列数组的顺序,使其刚好存在k个翻转对,所谓的翻转对,就是位置在前面的数字值大,而且题目中表明了结果会很大很大,要我们对一个很大的数字取余。对于这种结果巨大的题目,劝君放弃暴力破解或者是无脑递归,想都不用想,那么最先应该考虑的就是DP的解法了。我们需要一个二维的DP数组,其中dp[i][j]表示1到i的数字中有j个翻转对的排列总数,那么我们要求的就是dp[n][k]了,即1到n的数字中有k个翻转对的排列总数。现在难点就是要求递推公式了。我们想如果我们已经知道dp[n][k]了,怎么求dp[n+1][k],先来看dp[n+1][k]的含义,是1到n+1点数字中有k个翻转对的个数,那么实际上在1到n的数字中的某个位置加上了n+1这个数,为了简单起见,我们先让n=4,那么实际上相当于要在某个位置加上5,那么加5的位置就有如下几种情况:

    xxxx5

    xxx5x

    xx5xx

    x5xxx

    5xxxx

    这里xxxx表示1到4的任意排列,那么第一种情况xxxx5不会增加任何新的翻转对,因为xxxx中没有比5大的数字,而 xxx5x会新增加1个翻转对,xx5xx,x5xxx,5xxxx分别会增加2,3,4个翻转对。那么xxxx5就相当于dp[n][k],即dp[4][k],那么依次往前类推,就是dp[n][k-1], dp[n][k-2]...dp[n][k-n],这样我们就可以得出dp[n+1][k]的求法了:

    dp[n+1][k] = dp[n][k] + dp[n][k-1] + ... + dp[n][k - n]

    那么dp[n][k]的求法也就一目了然了:

    dp[n][k] = dp[n - 1][k] + dp[n - 1][k-1] + ... + dp[n - 1][k - n + 1]

    那么我们就可以写出代码如下了:

     1 class Solution {
     2     public int kInversePairs(int n, int k) {
     3         int M=1000000007;
     4         int[][] dp=new int[n+1][k+1];
     5         dp[0][0]=1;
     6         for(int i=0;i<=n;i++){
     7             for(int j=0;j<i;++j){
     8                 for(int m=0;m<=k;m++){
     9                     if(m-j>=0&&m-j<=k){
    10                         dp[i][m]=(dp[i][m]+dp[i-1][m-j])%M;
    11                     }
    12                 }
    13             }
    14         }
    15         return dp[n][k];
    16     }
    17 }

    我们可以对上面的解法进行时间上的优化,还是来看我们的递推公式: 

    dp[n][k] = dp[n - 1][k] + dp[n - 1][k-1] + ... + dp[n - 1][k - n + 1]

    我们可以用k+1代替k,得到:

    dp[n][k+1] = dp[n - 1][k+1] + dp[n - 1][k] + ... + dp[n - 1][k + 1 - n + 1]

    用第二个等式减去第一个等式可以得到:

    dp[n][k+1] = dp[n][k] + dp[n - 1][k+1] - dp[n - 1][k - n + 1]

    将k+1换回成k,可以得到:

    dp[n][k] = dp[n][k-1] + dp[n - 1][k] - dp[n - 1][k - n]

    我们可以发现当k>=n的时候,最后一项的数组坐标才能为非负数,从而最后一项才有值,所以我们再更新的时候只需要判断一下k和n的关系,如果k>=n的话,就要减去最后一项,这种递推式算起来更高效,减少了一个循环,参见代码如下:

     1 class Solution {
     2     public int kInversePairs(int n, int k) {
     3         int mo=1000000007;
     4         int[][] f=new int[1002][1002];
     5         f[1][0]=1;
     6         for (int i=2;i<=n;i++) {
     7             f[i][0]=1;
     8             for (int j=1;j<=k;j++) {
     9                 f[i][j]=(f[i][j-1]+f[i-1][j])%mo;
    10                 if (j>=i) f[i][j]=(f[i][j]-f[i-1][j-i]+mo)%mo;
    11             }
    12         }
    13         return f[n][k];
    14     }
    15 }
  • 相关阅读:
    C语言和python分别计算文件的md5值
    C语言计算文件大小
    Linux内核源码下载
    Linux系统编程20_VFS虚拟文件系统
    Linux系统编程19_标准I/O
    C语言Review5_函数指针和数组指针
    C语言Review4_头文件引用符号的区别
    PDO之MySql持久化自动重连导致内存溢出
    小程序之app.json not found
    phpstorm之"Can not run PHP Code Sniffer"
  • 原文地址:https://www.cnblogs.com/kexinxin/p/10381439.html
Copyright © 2020-2023  润新知