LeetCode 629: K Inverse Pairs Array (DP + Prefix Sum Optimization)
LeetCode 629Dynamic ProgrammingPrefix SumToday we solve LeetCode 629 - K Inverse Pairs Array.
Source: https://leetcode.com/problems/k-inverse-pairs-array/
English
Problem Summary
Count how many permutations of numbers 1..n have exactly k inverse pairs, and return the result modulo 1e9+7.
Key Insight
Let dp[i][j] be the number of arrays built from 1..i with exactly j inverse pairs. When inserting i into an array of length i-1, it can add from 0 to i-1 new inverse pairs.
Brute Force and Limitations
Brute force permutation generation is impossible for large n. A direct DP transition sums up to i terms per state, giving O(n*k*n). Prefix sums reduce each transition to O(1).
Optimal Algorithm Steps
- State: dp[i][j].
- Transition: dp[i][j] = sum(dp[i-1][j-x]) for x in [0, min(j, i-1)].
- Use prefix-sum form:
dp[i][j] = dp[i][j-1] + dp[i-1][j] - (j>=i ? dp[i-1][j-i] : 0).
- Apply modulo normalization at each step.
Complexity Analysis
Time: O(n*k).
Space: O(k) with rolling array.
Common Pitfalls
- Forgetting modulo after subtraction, causing negative values.
- Using non-rolling 2D DP unnecessarily.
- Not capping impossible k values (max inverse pairs is n*(n-1)/2).
Reference Implementations (Java / Go / C++ / Python / JavaScript)
class Solution {
private static final int MOD = 1_000_000_007;
public int kInversePairs(int n, int k) {
if (k > n * (n - 1) / 2) return 0;
int[] prev = new int[k + 1];
prev[0] = 1;
for (int i = 1; i <= n; i++) {
int[] curr = new int[k + 1];
curr[0] = 1;
for (int j = 1; j <= k; j++) {
long val = curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = (int) val;
}
prev = curr;
}
return prev[k];
}
}func kInversePairs(n int, k int) int {
const mod = 1_000_000_007
if k > n*(n-1)/2 {
return 0
}
prev := make([]int, k+1)
prev[0] = 1
for i := 1; i <= n; i++ {
curr := make([]int, k+1)
curr[0] = 1
for j := 1; j <= k; j++ {
val := curr[j-1] + prev[j]
if j >= i {
val -= prev[j-i]
}
val %= mod
if val < 0 {
val += mod
}
curr[j] = val
}
prev = curr
}
return prev[k]
}class Solution {
public:
int kInversePairs(int n, int k) {
const int MOD = 1'000'000'007;
if (k > n * (n - 1) / 2) return 0;
vector<int> prev(k + 1, 0);
prev[0] = 1;
for (int i = 1; i <= n; ++i) {
vector<int> curr(k + 1, 0);
curr[0] = 1;
for (int j = 1; j <= k; ++j) {
long long val = (long long)curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = (int)val;
}
prev.swap(curr);
}
return prev[k];
}
};class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 1_000_000_007
if k > n * (n - 1) // 2:
return 0
prev = [0] * (k + 1)
prev[0] = 1
for i in range(1, n + 1):
curr = [0] * (k + 1)
curr[0] = 1
for j in range(1, k + 1):
val = curr[j - 1] + prev[j]
if j >= i:
val -= prev[j - i]
curr[j] = val % MOD
prev = curr
return prev[k]/**
* @param {number} n
* @param {number} k
* @return {number}
*/
var kInversePairs = function(n, k) {
const MOD = 1000000007;
if (k > (n * (n - 1)) / 2) return 0;
let prev = new Array(k + 1).fill(0);
prev[0] = 1;
for (let i = 1; i <= n; i++) {
const curr = new Array(k + 1).fill(0);
curr[0] = 1;
for (let j = 1; j <= k; j++) {
let val = curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = val;
}
prev = curr;
}
return prev[k];
};中文
题目概述
统计由 1..n 组成的排列中,恰好包含 k 个逆序对的方案数,结果对 1e9+7 取模。
核心思路
定义 dp[i][j] 为使用数字 1..i 且逆序对数量为 j 的方案数。把数字 i 插入长度 i-1 的排列时,会新增 0..i-1 个逆序对。
暴力解法与不足
直接枚举排列不可行。朴素 DP 每个状态再枚举插入位置会变成 O(n*k*n)。使用前缀和优化后,每个状态可 O(1) 转移。
最优算法步骤
- 状态:dp[i][j]。
- 转移:dp[i][j] = sum(dp[i-1][j-x]),其中 x ∈ [0, min(j, i-1)]。
- 前缀和化简为:
dp[i][j] = dp[i][j-1] + dp[i-1][j] - (j>=i ? dp[i-1][j-i] : 0)。
- 每一步都做取模并处理负数。
复杂度分析
时间复杂度:O(n*k)。
空间复杂度:O(k)(滚动数组)。
常见陷阱
- 做减法后没有补模,导致负数。
- 不使用滚动数组,空间开销过大。
- 没有提前判断不可能的 k(最大逆序对是 n*(n-1)/2)。
多语言参考实现(Java / Go / C++ / Python / JavaScript)
class Solution {
private static final int MOD = 1_000_000_007;
public int kInversePairs(int n, int k) {
if (k > n * (n - 1) / 2) return 0;
int[] prev = new int[k + 1];
prev[0] = 1;
for (int i = 1; i <= n; i++) {
int[] curr = new int[k + 1];
curr[0] = 1;
for (int j = 1; j <= k; j++) {
long val = curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = (int) val;
}
prev = curr;
}
return prev[k];
}
}func kInversePairs(n int, k int) int {
const mod = 1_000_000_007
if k > n*(n-1)/2 {
return 0
}
prev := make([]int, k+1)
prev[0] = 1
for i := 1; i <= n; i++ {
curr := make([]int, k+1)
curr[0] = 1
for j := 1; j <= k; j++ {
val := curr[j-1] + prev[j]
if j >= i {
val -= prev[j-i]
}
val %= mod
if val < 0 {
val += mod
}
curr[j] = val
}
prev = curr
}
return prev[k]
}class Solution {
public:
int kInversePairs(int n, int k) {
const int MOD = 1'000'000'007;
if (k > n * (n - 1) / 2) return 0;
vector<int> prev(k + 1, 0);
prev[0] = 1;
for (int i = 1; i <= n; ++i) {
vector<int> curr(k + 1, 0);
curr[0] = 1;
for (int j = 1; j <= k; ++j) {
long long val = (long long)curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = (int)val;
}
prev.swap(curr);
}
return prev[k];
}
};class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 1_000_000_007
if k > n * (n - 1) // 2:
return 0
prev = [0] * (k + 1)
prev[0] = 1
for i in range(1, n + 1):
curr = [0] * (k + 1)
curr[0] = 1
for j in range(1, k + 1):
val = curr[j - 1] + prev[j]
if j >= i:
val -= prev[j - i]
curr[j] = val % MOD
prev = curr
return prev[k]/**
* @param {number} n
* @param {number} k
* @return {number}
*/
var kInversePairs = function(n, k) {
const MOD = 1000000007;
if (k > (n * (n - 1)) / 2) return 0;
let prev = new Array(k + 1).fill(0);
prev[0] = 1;
for (let i = 1; i <= n; i++) {
const curr = new Array(k + 1).fill(0);
curr[0] = 1;
for (let j = 1; j <= k; j++) {
let val = curr[j - 1] + prev[j];
if (j >= i) val -= prev[j - i];
val %= MOD;
if (val < 0) val += MOD;
curr[j] = val;
}
prev = curr;
}
return prev[k];
};
Comments