[LeetCode] K Inverse Pairs Array K个翻转对数组

简介: Given two integers n and k, find how many different arrays consist of numbers from 1 to n such that there are exactly k inverse pairs.

Given two integers n and k, find how many different arrays consist of numbers from 1 to n such that there are exactly k inverse pairs.

We define an inverse pair as following: For ith and jth element in the array, if i < j and a[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:

  1. The integer n is in the range [1, 1000] and k is in the range [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]

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

解法一:

class Solution {
public:
    int kInversePairs(int n, int k) {
        int M = 1000000007;
        vector<vector<int>> dp(n + 1, vector<int>(k + 1, 0));
        dp[0][0] = 1;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j < i; ++j) {
                for (int m = 0; m <= k; ++m) {
                    if (m - j >= 0 && m - j <= k) {
                        dp[i][m] = (dp[i][m] + dp[i - 1][m - j]) % M;
                    }
                }
            }
        }
        return dp[n][k];
    }
};

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

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的话,就要减去最后一项,这种递推式算起来更高效,减少了一个循环,参见代码如下:

解法二:

public:
    int kInversePairs(int n, int k) {
        int M = 1000000007;
        vector<vector<int>> dp(n + 1, vector<int>(k + 1, 0));
        dp[0][0] = 1;
        for (int i = 1; i <= n; ++i) {
            dp[i][0] = 1;
            for (int j = 1; j <= k; ++j) {
                dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % M;
                if (j >= i) {
                    dp[i][j] = (dp[i][j] - dp[i - 1][j - i] + M) % M;
                }
            }
        }
        return dp[n][k];
    }
};

参考资料:

https://discuss.leetcode.com/topic/93815/java-dp-o-nk-solution/2

https://discuss.leetcode.com/topic/93765/shared-my-c-o-n-k-solution-with-explanation

本文转自博客园Grandyang的博客,原文链接:[LeetCode] K Inverse Pairs Array K个翻转对数组

,如需转载请自行联系原博主。

相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
1月前
|
人工智能 前端开发 JavaScript
拿下奇怪的前端报错(一):报错信息是一个看不懂的数字数组Buffer(475) [Uint8Array],让AI大模型帮忙解析
本文介绍了前端开发中遇到的奇怪报错问题,特别是当错误信息不明确时的处理方法。作者分享了自己通过还原代码、试错等方式解决问题的经验,并以一个Vue3+TypeScript项目的构建失败为例,详细解析了如何从错误信息中定位问题,最终通过解读错误信息中的ASCII码找到了具体的错误文件。文章强调了基础知识的重要性,并鼓励读者遇到类似问题时不要慌张,耐心分析。
|
1月前
|
存储 Java
Java“(array) <X> Not Initialized” (数组未初始化)错误解决
在Java中,遇到“(array) &lt;X&gt; Not Initialized”(数组未初始化)错误时,表示数组变量已被声明但尚未初始化。解决方法是在使用数组之前,通过指定数组的大小和类型来初始化数组,例如:`int[] arr = new int[5];` 或 `String[] strArr = new String[10];`。
|
1月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
19 4
|
1月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
18 0
Leetcode第三十三题(搜索旋转排序数组)
|
1月前
【LeetCode】整数翻转
【LeetCode】整数翻转
15 1
|
1月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
57 0
|
1月前
|
C++
【LeetCode 12】349.两个数组的交集
【LeetCode 12】349.两个数组的交集
16 0
|
1月前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
|
2月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行