629. K Inverse Pairs Array - Explanation
Description
Consider all permutations of the integers 1 through n, using each integer exactly once. An inverse pair consists of indices i < j whose values satisfy nums[i] > nums[j]. Return how many permutations have exactly k inverse pairs, modulo 1000000007.
Example 1:
Input: n = 3
k = 2
Output: 2Explanation: The permutations [2,3,1] and [3,1,2] each contain exactly two inverse pairs. No other permutation of 1, 2, and 3 has exactly two.
Example 2:
Input: n = 3
k = 3
Output: 1Explanation: Only [3,2,1] has three inverse pairs: the value pairs (3,2), (3,1), and (2,1).
Example 3:
Input: n = 2
k = 2
Output: 0Explanation: A permutation of two elements has only one pair of indices, so it cannot contain two inverse pairs.
Constraints:
1 <= n <= 10000 <= k <= 1000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Dynamic Programming - Building solutions from smaller subproblems using both top-down (memoization) and bottom-up (tabulation) approaches
- Inverse Pairs in Permutations - Understanding that an inverse pair (i, j) means i < j but arr[i] > arr[j]
- Modular Arithmetic - Performing addition and subtraction under modulo to prevent integer overflow
- Sliding Window Optimization - Recognizing when consecutive DP states share overlapping terms that can be computed incrementally
- Prefix Sums - Using cumulative sums to compute range queries in constant time
1. Dynamic Programming (Top-Down)
Intuition
When placing the number n into a permutation of n - 1 elements, we can create 0 to n - 1 new inverse pairs depending on its position. Placing n at the end creates 0 new pairs, while placing it at the start creates n - 1 pairs since n is larger than all other elements. This gives us a recurrence: the count for (n, k) equals the sum of counts for (n - 1, k), (n - 1, k - 1), ..., (n - 1, k - (n - 1)).
Algorithm
- Define
count(n, k)as the number of permutations ofnelements with exactlykinverse pairs. - Base case: If
n == 0, return1ifk == 0, else return0. - For each position where we can place
n, it createsiinverse pairs (whereigoes from0ton - 1). - Sum up
count(n - 1, k - i)for all validi. - Cache results to avoid recomputation.
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10**9 + 7
cache = {}
def count(n, k):
if n == 0:
return 1 if k == 0 else 0
if k < 0:
return 0
if (n, k) in cache:
return cache[(n, k)]
cache[(n, k)] = 0
for i in range(n):
cache[(n, k)] = (cache[(n, k)] + count(n - 1, k - i)) % MOD
return cache[(n, k)]
return count(n, k)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the permutation and is the number of inverse pairs in the permutation.
2. Dynamic Programming (Top-Down Optimized)
Intuition
The basic recurrence sums up to n terms. We can optimize using the relationship: count(n, k) = count(n, k - 1) + count(n - 1, k) - count(n - 1, k - n). This comes from observing that count(n, k) and count(n, k - 1) share most terms, differing only at the boundaries. We also add early termination when k exceeds the maximum possible inverse pairs for n elements, which is n * (n - 1) / 2.
Algorithm
- Base cases: Return
1ifk == 0, return0ifn == 1, and handle bounds based on max pairs. - Use the optimized recurrence:
count(n, k) = count(n, k - 1) + count(n - 1, k) - count(n - 1, k - n). - The subtraction handles the term that falls outside the valid range when shifting from
k - 1tok. - Apply modular arithmetic to keep numbers manageable.
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[-1] * (k + 1) for _ in range(n + 1)]
def count(n, k):
if k == 0:
return 1
if n == 1:
return 0
if n * (n - 1) // 2 < k:
return 0
if n * (n - 1) // 2 == k:
return 1
if dp[n][k] != -1:
return dp[n][k]
res = count(n, k - 1)
if k >= n:
res -= count(n - 1, k - n)
res = (res + count(n - 1, k)) % MOD
dp[n][k] = res
return res
return count(n, k)Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the permutation and is the number of inverse pairs in the permutation.
3. Dynamic Programming (Bottom-Up)
Intuition
We build up the solution starting from smaller values of n. For each (N, K) state, we sum contributions from placing element N at different positions, each creating a different number of new inverse pairs. This iterative approach avoids recursion overhead and naturally fills the DP table row by row.
Algorithm
- Create a 2D DP table with
dp[0][0] = 1. - For each
Nfrom1ton:- For each
Kfrom0tok:- Sum up
dp[N - 1][K - pairs]forpairsfrom0toN - 1whereK - pairs >= 0.
- Sum up
- For each
- Return
dp[n][k].
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[0] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 1
for N in range(1, n + 1):
for K in range(k + 1):
for pairs in range(N):
if K - pairs >= 0:
dp[N][K] = (dp[N][K] + dp[N - 1][K - pairs]) % MOD
return dp[n][k]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the permutation and is the number of inverse pairs in the permutation.
4. Dynamic Programming (Bottom-Up Optimized)
Intuition
Rather than summing N terms for each cell, we use the sliding window observation from the top-down optimized approach. The value at dp[N][K] builds on dp[N][K - 1] by adding dp[N - 1][K] (the new term entering the window) and subtracting dp[N - 1][K - N] (the term leaving the window). This reduces each cell computation to constant time.
Algorithm
- Create a 2D DP table with
dp[0][0] = 1. - For each
Nfrom1ton, and eachKfrom0tok:- Start with
dp[N][K] = dp[N - 1][K]. - If
K > 0, adddp[N][K - 1](cumulative sum from left). - If
K >= N, subtractdp[N - 1][K - N](remove out-of-window term).
- Start with
- Return
dp[n][k].
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[0] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 1
for N in range(1, n + 1):
for K in range(k + 1):
dp[N][K] = dp[N - 1][K]
if K > 0:
dp[N][K] = (dp[N][K] + dp[N][K - 1]) % MOD
if K >= N:
dp[N][K] = (dp[N][K] - dp[N - 1][K - N] + MOD) % MOD
return dp[n][k]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the permutation and is the number of inverse pairs in the permutation.
5. Dynamic Programming (Space Optimized)
Intuition
Since each row only depends on the previous row, we only need to keep two 1D arrays instead of the full 2D table. We maintain a running total that acts as a prefix sum, adding new values and subtracting values that fall outside the window of size N.
Algorithm
- Initialize
prevarray withprev[0] = 1. - For each
Nfrom1ton:- Create a new
curarray and maintain a runningtotal. - For each
Kfrom0tok:- Add
prev[K]tototal. - If
K >= N, subtractprev[K - N]fromtotal. - Set
cur[K] = total.
- Add
- Swap
prev = curfor the next iteration.
- Create a new
- Return
prev[k].
class Solution:
def kInversePairs(self, n: int, k: int) -> int:
MOD = 10**9 + 7
prev = [0] * (k + 1)
prev[0] = 1
for N in range(1, n + 1):
cur = [0] * (k + 1)
total = 0
for K in range(0, k + 1):
total = (total + prev[K]) % MOD
if K >= N:
total = (total - prev[K - N] + MOD) % MOD
cur[K] = total
prev = cur
return prev[k]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the size of the permutation and is the number of inverse pairs in the permutation.
Common Pitfalls
Forgetting Modular Arithmetic
Results can grow extremely large, requiring modulo 10^9 + 7 operations. Forgetting to apply the modulo after each addition causes integer overflow. Apply % MOD consistently in all accumulation steps.
Incorrect Handling of Negative Values in Modular Subtraction
When subtracting in the optimized recurrence (res - dp[n-1][k-n]), the result may become negative before taking modulo. Always add MOD before the final modulo: (res - value + MOD) % MOD to ensure a non-negative result.
Not Recognizing the Sliding Window Pattern
The naive approach sums n terms per cell, leading to O(n^2 * k) complexity. The key insight is that consecutive cells share most terms, allowing a sliding window optimization. Missing this pattern results in TLE on large inputs.
Off-by-One Errors in Window Boundaries
When placing element n, it can create 0 to n-1 inverse pairs (not n pairs). The window of previous row values spans k down to k - (n-1). Getting these boundaries wrong produces incorrect counts.
Missing Base Cases
The recurrence requires proper initialization: dp[0][0] = 1 (one way to arrange zero elements with zero pairs). Missing this base case or incorrectly setting dp[n][0] values propagates errors through the entire table.
Sign in to join the discussion