629. K Inverse Pairs Array
Problem Description
Given two integers n
and k
, you need to count how many different arrays of length n
containing numbers from 1
to n
have exactly k
inverse pairs.
An inverse pair in an array is defined as a pair of indices [i, j]
where:
0 <= i < j < array.length
array[i] > array[j]
In other words, an inverse pair occurs when a larger number appears before a smaller number in the array.
For example:
- Array
[1, 3, 2]
has 1 inverse pair:[1, 2]
(since3 > 2
) - Array
[3, 2, 1]
has 3 inverse pairs:[0, 1]
,[0, 2]
, and[1, 2]
- Array
[1, 2, 3]
has 0 inverse pairs
The task is to find the total number of permutations of numbers from 1
to n
that result in exactly k
inverse pairs. Since the answer can be very large, return the result modulo 10^9 + 7
.
Intuition
Let's think about how to build arrays with a specific number of inverse pairs step by step.
Start with a smaller problem: if we have an array of length i-1
with some number of inverse pairs, what happens when we add the number i
to it?
The key insight is that when we insert the largest number i
into different positions of an existing array, we can control exactly how many new inverse pairs we create:
- If we place
i
at the end (rightmost position), it creates0
new inverse pairs (sincei
is larger than all previous numbers) - If we place
i
at the second-to-last position, it creates1
new inverse pair (with the number that was originally at the last position) - If we place
i
at the third-to-last position, it creates2
new inverse pairs - ...
- If we place
i
at the beginning (leftmost position), it createsi-1
new inverse pairs (with all other numbers)
This means if we know the number of ways to create arrays of length i-1
with j
inverse pairs, we can compute the number of ways to create arrays of length i
with k
inverse pairs by considering all valid placements of the number i
.
For f[i][k]
(arrays of length i
with k
inverse pairs), we sum up:
- Ways to have
k
inverse pairs by placingi
at the end (needk
inverse pairs from the firsti-1
numbers) - Ways to have
k
inverse pairs by placingi
second-to-last (needk-1
inverse pairs from the firsti-1
numbers) - ...
- Ways to have
k
inverse pairs by placingi
at the beginning (needk-(i-1)
inverse pairs from the firsti-1
numbers)
This gives us the recurrence: f[i][k] = f[i-1][k] + f[i-1][k-1] + ... + f[i-1][k-(i-1)]
The sum pattern suggests we can optimize using prefix sums to avoid recalculating overlapping ranges, and since each state only depends on the previous row, we can optimize space by using a rolling array.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using dynamic programming with space optimization and prefix sum optimization.
Dynamic Programming Setup:
Define f[j]
as the number of arrays of the current length with j
inverse pairs. We iterate through lengths from 1
to n
, updating this array for each length.
Initially, f[0] = 1
(one way to have 0 inverse pairs with any length), and all other f[j] = 0
.
Prefix Sum Optimization:
Since calculating f[i][j]
requires summing f[i-1][j] + f[i-1][j-1] + ... + f[i-1][max(0, j-(i-1))]
, we use a prefix sum array s
to efficiently compute these range sums.
The prefix sum array s[j]
stores the cumulative sum: s[j] = f[0] + f[1] + ... + f[j-1]
Implementation Steps:
-
Initialize arrays:
f = [1] + [0] * k
: Start withf[0] = 1
s = [0] * (k + 2)
: Prefix sum array with extra space for boundary handling
-
For each array length
i
from1
ton
:-
Update
f[j]
for eachj
from1
tok
:- The number of inverse pairs
j
can be formed by placing numberi
at different positions - We need the sum of
f[i-1][j-p]
wherep
ranges from0
tomin(j, i-1)
- Using prefix sums:
f[j] = s[j+1] - s[max(0, j-(i-1))]
- Apply modulo to keep numbers manageable
- The number of inverse pairs
-
Rebuild prefix sum array for next iteration:
s[j] = s[j-1] + f[j-1]
for each position- This prepares the prefix sums for the next length
-
-
Return
f[k]
: The final answer for arrays of lengthn
with exactlyk
inverse pairs
Space Optimization:
Instead of maintaining a 2D array f[i][j]
, we use a 1D array that gets updated in place. This works because:
- Each
f[i][j]
only depends on values fromf[i-1][...]
- We process in order, so by the time we calculate
f[j]
, we've already used the oldf[j]
value
Time Complexity: O(n * k)
- we iterate through n
lengths and k
inverse pairs for each
Space Complexity: O(k)
- we only maintain arrays of size proportional to k
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through finding the number of permutations of [1, 2, 3]
(n=3) with exactly k=2 inverse pairs.
Initial Setup:
f = [1, 0, 0]
(f[j] represents ways to form j inverse pairs)s = [0, 0, 0, 0]
(prefix sum array)
Iteration 1: Adding number 1 (i=1)
- Arrays of length 1 can only have 0 inverse pairs:
[1]
- No updates needed since k > 0
- Update prefix sums:
s = [0, 1, 1, 1]
Iteration 2: Adding number 2 (i=2)
-
We can place 2 in two positions in arrays containing just 1:
[1, 2]
- placing 2 at end creates 0 new inverse pairs (total: 0)[2, 1]
- placing 2 at start creates 1 new inverse pair (total: 1)
-
For j=1:
f[1] = s[2] - s[max(0, 1-1)] = s[2] - s[0] = 1 - 0 = 1
- This counts:
[2, 1]
(1 way)
- This counts:
-
For j=2:
f[2] = s[3] - s[max(0, 2-1)] = s[3] - s[1] = 1 - 1 = 0
- No arrays of length 2 can have 2 inverse pairs
-
Result:
f = [1, 1, 0]
-
Update prefix sums:
s = [0, 1, 2, 2]
Iteration 3: Adding number 3 (i=3)
-
We can place 3 in three positions in existing arrays:
- At end (position 2): creates 0 new inverse pairs
- At middle (position 1): creates 1 new inverse pair
- At start (position 0): creates 2 new inverse pairs
-
For j=1:
f[1] = s[2] - s[max(0, 1-2)] = s[2] - s[0] = 2 - 0 = 2
- From
[1, 2]
(0 inversions): place 3 at position 1 →[1, 3, 2]
(1 inversion) - From
[2, 1]
(1 inversion): place 3 at end →[2, 1, 3]
(1 inversion)
- From
-
For j=2:
f[2] = s[3] - s[max(0, 2-2)] = s[3] - s[0] = 2 - 0 = 2
- From
[1, 2]
(0 inversions): place 3 at position 0 →[3, 1, 2]
(2 inversions) - From
[2, 1]
(1 inversion): place 3 at position 1 →[2, 3, 1]
(2 inversions)
- From
Final Answer: f[2] = 2
The two permutations of [1, 2, 3]
with exactly 2 inverse pairs are:
[3, 1, 2]
- inverse pairs: (0,1) and (0,2)[2, 3, 1]
- inverse pairs: (0,2) and (1,2)
Solution Implementation
1class Solution:
2 def kInversePairs(self, n: int, k: int) -> int:
3 """
4 Find the number of different arrays consisting of numbers from 1 to n
5 such that there are exactly k inverse pairs.
6
7 An inverse pair is a pair (i, j) where i < j but arr[i] > arr[j].
8
9 Args:
10 n: The range of numbers [1, n] to form the array
11 k: The target number of inverse pairs
12
13 Returns:
14 The count of valid arrays modulo 10^9 + 7
15 """
16 MOD = 10**9 + 7
17
18 # dp[j] represents the number of permutations with exactly j inverse pairs
19 # Initially, with 0 numbers, there's 1 way to have 0 inverse pairs (empty permutation)
20 dp = [1] + [0] * k
21
22 # prefix_sum[j] stores the cumulative sum of dp[0] to dp[j-1]
23 # Used for range sum queries to optimize the DP transition
24 prefix_sum = [0] * (k + 2)
25
26 # Build up permutations by adding numbers from 1 to n
27 for current_number in range(1, n + 1):
28 # Calculate new dp values for current_number elements
29 for inverse_pairs in range(1, k + 1):
30 # When inserting current_number into a permutation of (current_number - 1) elements,
31 # we can place it at positions creating 0 to min(inverse_pairs, current_number - 1) new inverse pairs
32 #
33 # The range of valid previous inverse pairs is:
34 # [max(0, inverse_pairs - (current_number - 1)), inverse_pairs]
35 min_prev_pairs = max(0, inverse_pairs - (current_number - 1))
36
37 # Sum dp values in the valid range using prefix sums
38 dp[inverse_pairs] = (prefix_sum[inverse_pairs + 1] - prefix_sum[min_prev_pairs]) % MOD
39
40 # Update prefix sum array for the next iteration
41 for j in range(1, k + 2):
42 prefix_sum[j] = (prefix_sum[j - 1] + dp[j - 1]) % MOD
43
44 return dp[k]
45
1class Solution {
2 public int kInversePairs(int n, int k) {
3 // Modulo value for large number handling
4 final int MOD = 1_000_000_007;
5
6 // dp[j] represents the number of permutations with exactly j inverse pairs
7 // using numbers from 1 to current i
8 int[] dp = new int[k + 1];
9
10 // prefixSum[j] stores the prefix sum of dp array up to index j-1
11 // This helps optimize the range sum calculation
12 int[] prefixSum = new int[k + 2];
13
14 // Base case: empty permutation has 0 inverse pairs
15 dp[0] = 1;
16
17 // Initialize prefix sum array (all 1s except index 0)
18 Arrays.fill(prefixSum, 1);
19 prefixSum[0] = 0;
20
21 // Build up the solution for each number from 1 to n
22 for (int i = 1; i <= n; ++i) {
23 // Calculate dp values for current i
24 for (int j = 1; j <= k; ++j) {
25 // When inserting number i into a permutation of (i-1) numbers,
26 // we can create 0 to min(j, i-1) new inverse pairs
27 // dp[i][j] = sum of dp[i-1][j-min(j, i-1)] to dp[i-1][j]
28 int minInversePairs = Math.max(0, j - (i - 1));
29 dp[j] = (prefixSum[j + 1] - prefixSum[minInversePairs] + MOD) % MOD;
30 }
31
32 // Update prefix sum array for next iteration
33 for (int j = 1; j <= k + 1; ++j) {
34 prefixSum[j] = (prefixSum[j - 1] + dp[j - 1]) % MOD;
35 }
36 }
37
38 // Return the number of permutations with exactly k inverse pairs
39 return dp[k];
40 }
41}
42
1class Solution {
2public:
3 int kInversePairs(int n, int k) {
4 // dp[j] represents the number of permutations with exactly j inverse pairs
5 // for the current number of elements being considered
6 int dp[k + 1];
7
8 // prefixSum[j] stores the prefix sum up to index j-1 of dp array
9 // This is used for range sum queries to optimize the DP transition
10 int prefixSum[k + 2];
11
12 // Initialize dp array with zeros
13 memset(dp, 0, sizeof(dp));
14
15 // Base case: empty permutation has 0 inverse pairs
16 dp[0] = 1;
17
18 // Initialize prefix sum array
19 // prefixSum[0] = 0, prefixSum[1..k+1] = 1 initially
20 fill(prefixSum, prefixSum + k + 2, 1);
21 prefixSum[0] = 0;
22
23 const int MOD = 1e9 + 7;
24
25 // Build up the solution by adding numbers 1 to n one by one
26 for (int currentNum = 1; currentNum <= n; ++currentNum) {
27 // For each possible number of inverse pairs
28 for (int inversePairs = 1; inversePairs <= k; ++inversePairs) {
29 // When adding currentNum to permutations of (currentNum-1) numbers,
30 // we can place it at positions that create 0 to min(inversePairs, currentNum-1) new inverse pairs
31 // This corresponds to summing dp values from previous iteration
32 int maxNewPairs = currentNum - 1;
33 int rangeStart = max(0, inversePairs - maxNewPairs);
34
35 // Use prefix sum for efficient range sum query
36 dp[inversePairs] = (prefixSum[inversePairs + 1] - prefixSum[rangeStart] + MOD) % MOD;
37 }
38
39 // Rebuild the prefix sum array for the next iteration
40 for (int j = 1; j <= k + 1; ++j) {
41 prefixSum[j] = (prefixSum[j - 1] + dp[j - 1]) % MOD;
42 }
43 }
44
45 // Return the number of permutations of n numbers with exactly k inverse pairs
46 return dp[k];
47 }
48};
49
1/**
2 * Counts the number of different arrays consisting of numbers from 1 to n
3 * such that there are exactly k inverse pairs.
4 * An inverse pair is a pair of integers [i, j] where 0 <= i < j < n and a[i] > a[j].
5 *
6 * @param n - The upper bound of numbers in the array (1 to n)
7 * @param k - The exact number of inverse pairs required
8 * @returns The number of different arrays modulo 10^9 + 7
9 */
10function kInversePairs(n: number, k: number): number {
11 const MOD: number = 1000000007;
12
13 // dp[j] represents the number of permutations with exactly j inverse pairs
14 // for the current iteration (current value of i)
15 const dp: number[] = Array(k + 1).fill(0);
16 dp[0] = 1; // Base case: one way to have 0 inverse pairs
17
18 // prefixSum[j] stores the prefix sum of dp array up to index j-1
19 // Used for optimization to calculate range sums in O(1)
20 const prefixSum: number[] = Array(k + 2).fill(1);
21 prefixSum[0] = 0;
22
23 // Iterate through each number from 1 to n
24 for (let i = 1; i <= n; ++i) {
25 // Calculate dp values for current i
26 for (let j = 1; j <= k; ++j) {
27 // When adding number i to permutations of (i-1) numbers,
28 // we can create 0 to min(j, i-1) new inverse pairs
29 // Sum all valid previous states using prefix sum optimization
30 const minInversePairs: number = Math.max(0, j - (i - 1));
31 dp[j] = (prefixSum[j + 1] - prefixSum[minInversePairs] + MOD) % MOD;
32 }
33
34 // Update prefix sum array for next iteration
35 for (let j = 1; j <= k + 1; ++j) {
36 prefixSum[j] = (prefixSum[j - 1] + dp[j - 1]) % MOD;
37 }
38 }
39
40 return dp[k];
41}
42
Time and Space Complexity
The time complexity is O(n × k)
, where n
is the number of elements in the permutation and k
is the target number of inverse pairs. This is because we have two nested loops: the outer loop iterates n
times (from 1 to n), and for each iteration, the inner loop iterates k
times to update the dynamic programming array f[j]
. Additionally, there's another loop that updates the prefix sum array s
, which also runs k + 1
times, but this doesn't change the overall complexity as it's still O(k)
for each outer iteration.
The space complexity is O(k)
. The algorithm uses two arrays: array f
of size k + 1
to store the number of permutations with exactly j
inverse pairs, and array s
of size k + 2
to store the prefix sums. Both arrays have space proportional to k
, resulting in O(k)
space complexity overall.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors in Prefix Sum Array Indexing
One of the most common mistakes in this solution is incorrectly handling the prefix sum array indices. The prefix sum array needs careful indexing because:
prefix_sum[j]
stores the sum ofdp[0]
throughdp[j-1]
- When calculating range sums, you need
prefix_sum[j+1] - prefix_sum[min_prev_pairs]
Incorrect Implementation:
# Wrong: Using incorrect indices for prefix sum dp[inverse_pairs] = (prefix_sum[inverse_pairs] - prefix_sum[min_prev_pairs - 1]) % MOD
Correct Implementation:
# Correct: Proper prefix sum indexing dp[inverse_pairs] = (prefix_sum[inverse_pairs + 1] - prefix_sum[min_prev_pairs]) % MOD
2. Forgetting to Handle Negative Numbers After Modulo
In Python, the modulo operation preserves sign, but when subtracting values that have been taken modulo, you might get negative results that need correction.
Incorrect Implementation:
# Wrong: Can produce negative results in some languages/contexts dp[inverse_pairs] = (prefix_sum[inverse_pairs + 1] - prefix_sum[min_prev_pairs]) % MOD
Safer Implementation:
# Better: Ensure non-negative result diff = prefix_sum[inverse_pairs + 1] - prefix_sum[min_prev_pairs] dp[inverse_pairs] = (diff % MOD + MOD) % MOD
3. Not Properly Resetting the Prefix Sum Array
The prefix sum array must be rebuilt completely after each iteration for the current number. A common mistake is trying to update it incrementally without proper initialization.
Incorrect Implementation:
# Wrong: Not resetting prefix_sum properly
for j in range(1, k + 2):
prefix_sum[j] += dp[j - 1] # Accumulating without reset
Correct Implementation:
# Correct: Rebuild prefix_sum from scratch each iteration
for j in range(1, k + 2):
prefix_sum[j] = (prefix_sum[j - 1] + dp[j - 1]) % MOD
4. Incorrect Calculation of Maximum Possible New Inverse Pairs
When inserting number i
into a permutation, it can create at most i-1
new inverse pairs (when placed at the beginning). The valid range calculation often trips people up.
Incorrect Implementation:
# Wrong: Using current_number instead of current_number - 1
min_prev_pairs = max(0, inverse_pairs - current_number)
Correct Implementation:
# Correct: Maximum new inverse pairs is current_number - 1
min_prev_pairs = max(0, inverse_pairs - (current_number - 1))
5. Updating DP Array in Wrong Order
Since we're using space optimization with a 1D array, the order of updates matters. We must ensure we don't overwrite values we still need.
Incorrect Implementation:
# Wrong: Updating from small to large might use already-updated values
for inverse_pairs in range(k + 1):
dp[inverse_pairs] = calculated_value
Correct Implementation:
# Correct: Start from 1 to avoid using updated values prematurely
for inverse_pairs in range(1, k + 1):
dp[inverse_pairs] = calculated_value
# Note: dp[0] stays 1 throughout as there's always exactly one way to have 0 inverse pairs
How does merge sort divide the problem into subproblems?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!