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 of n elements with exactly k inverse pairs.
Base case: If n == 0, return 1 if k == 0, else return 0.
For each position where we can place n, it creates i inverse pairs (where i goes from 0 to n - 1).
classSolution:defkInversePairs(self, n:int, k:int)->int:
MOD =10**9+7
cache ={}defcount(n, k):if n ==0:return1if k ==0else0if k <0:return0if(n, k)in cache:return cache[(n, k)]
cache[(n, k)]=0for i inrange(n):
cache[(n, k)]=(cache[(n, k)]+ count(n -1, k - i))% MOD
return cache[(n, k)]return count(n, k)
publicclassSolution{privatestaticfinalintMOD=1_000_000_007;privateint[][] dp;publicintkInversePairs(int n,int k){
dp =newint[n +1][k +1];for(int i =0; i <= n; i++){for(int j =0; j <= k; j++){
dp[i][j]=-1;}}returncount(n, k);}privateintcount(int n,int k){if(n ==0)return k ==0?1:0;if(k <0)return0;if(dp[n][k]!=-1)return dp[n][k];int res =0;for(int i =0; i < n; i++){
res =(res +count(n -1, k - i))%MOD;}
dp[n][k]= res;return res;}}
classSolution{private:staticconstint MOD =1e9+7;
vector<vector<int>> dp;intcount(int n,int k){if(n ==0){return k ==0?1:0;}if(k <0){return0;}if(dp[n][k]!=-1){return dp[n][k];}int res =0;for(int i =0; i < n;++i){
res =(res +count(n -1, k - i))% MOD;}
dp[n][k]= res;return res;}public:intkInversePairs(int n,int k){
dp.assign(n +1,vector<int>(k +1,-1));returncount(n, k);}};
classSolution{/**
* @param {number} n
* @param {number} k
* @return {number}
*/kInversePairs(n, k){constMOD=1e9+7;const dp = Array.from({length: n +1},()=>Array(k +1).fill(-1));constcount=(n, k)=>{if(n ===0)return k ===0?1:0;if(k <0)return0;if(dp[n][k]!==-1)return dp[n][k];let res =0;for(let i =0; i < n; i++){
res =(res +count(n -1, k - i))%MOD;}
dp[n][k]= res;return res;};returncount(n, k);}}
publicclassSolution{privatestaticreadonlyint MOD =1000000007;privateint[,] dp;publicintKInversePairs(int n,int k){
dp =newint[n +1, k +1];for(int i =0; i <= n; i++){for(int j =0; j <= k; j++){
dp[i, j]=-1;}}returnCount(n, k);}privateintCount(int n,int k){if(n ==0)return k ==0?1:0;if(k <0)return0;if(dp[n, k]!=-1)return dp[n, k];int res =0;for(int i =0; i < n; i++){
res =(res +Count(n -1, k - i))% MOD;}
dp[n, k]= res;return res;}}
funckInversePairs(n int, k int)int{
MOD :=1000000007
dp :=make([][]int, n+1)for i :=range dp {
dp[i]=make([]int, k+1)for j :=range dp[i]{
dp[i][j]=-1}}var count func(n, k int)int
count =func(n, k int)int{if n ==0{if k ==0{return1}return0}if k <0{return0}if dp[n][k]!=-1{return dp[n][k]}
res :=0for i :=0; i < n; i++{
res =(res +count(n-1, k-i))% MOD
}
dp[n][k]= res
return res
}returncount(n, k)}
class Solution {privateval MOD =1000000007privatelateinitvar dp: Array<IntArray>funkInversePairs(n: Int, k: Int): Int {
dp =Array(n +1){IntArray(k +1){-1}}returncount(n, k)}privatefuncount(n: Int, k: Int): Int {if(n ==0)returnif(k ==0)1else0if(k <0)return0if(dp[n][k]!=-1)return dp[n][k]var res =0for(i in0 until n){
res =(res +count(n -1, k - i))% MOD
}
dp[n][k]= res
return res
}}
classSolution{letMOD=1000000007var dp:[[Int]]=[]funckInversePairs(_ n:Int,_ k:Int)->Int{
dp =[[Int]](repeating:[Int](repeating:-1, count: k +1), count: n +1)returncount(n, k)}privatefunccount(_ n:Int,_ k:Int)->Int{if n ==0{return k ==0?1:0}if k <0{return0}if dp[n][k]!=-1{return dp[n][k]}var res =0for i in0..<n {
res =(res +count(n -1, k - i))%MOD}
dp[n][k]= res
return res
}}
Time & Space Complexity
Time complexity: O(n2∗k)
Space complexity: O(n∗k)
Where n is the size of the permutation and k 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 1 if k == 0, return 0 if n == 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 - 1 to k.
Apply modular arithmetic to keep numbers manageable.
classSolution:defkInversePairs(self, n:int, k:int)->int:
MOD =10**9+7
dp =[[-1]*(k +1)for _ inrange(n +1)]defcount(n, k):if k ==0:return1if n ==1:return0if n *(n -1)//2< k:return0if n *(n -1)//2== k:return1if 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)
publicclassSolution{privatestaticfinalintMOD=1_000_000_007;privateint[][] dp;publicintkInversePairs(int n,int k){
dp =newint[n +1][k +1];for(int[] row : dp){Arrays.fill(row,-1);}returncount(n, k);}privateintcount(int n,int k){if(k ==0)return1;if(n ==1)return0;if(n *(n -1)/2< k)return0;if(n *(n -1)/2== k)return1;if(dp[n][k]!=-1)return dp[n][k];long res =count(n, k -1);if(k >= n){
res -=count(n -1, k - n);}
res =(res +count(n -1, k))%MOD;
dp[n][k]=(int)(res +MOD)%MOD;return dp[n][k];}}
classSolution{private:staticconstint MOD =1e9+7;
vector<vector<int>> dp;intcount(int n,int k){if(k ==0)return1;if(n ==1)return0;if(n *(n -1)/2< k)return0;if(n *(n -1)/2== k)return1;if(dp[n][k]!=-1)return dp[n][k];longlong res =count(n, k -1);if(k >= n){
res -=count(n -1, k - n);}
res =(res +count(n -1, k)+ MOD)% MOD;
dp[n][k]=int(res);return dp[n][k];}public:intkInversePairs(int n,int k){
dp.assign(n +1,vector<int>(k +1,-1));returncount(n, k);}};
classSolution{/**
* @param {number} n
* @param {number} k
* @return {number}
*/kInversePairs(n, k){constMOD=1e9+7;const dp = Array.from({length: n +1},()=>Array(k +1).fill(-1));constcount=(n, k)=>{if(k ===0)return1;if(n ===1)return0;if((n *(n -1))/2< k)return0;if((n *(n -1))/2=== k)return1;if(dp[n][k]!==-1)return dp[n][k];let res =count(n, k -1);if(k >= n){
res -=count(n -1, k - n);}
res =(res +count(n -1, k)+MOD)%MOD;
dp[n][k]= res;return res;};returncount(n, k);}}
publicclassSolution{privatestaticreadonlyint MOD =1000000007;privateint[,] dp;publicintKInversePairs(int n,int k){
dp =newint[n +1, k +1];for(int i =0; i <= n; i++){for(int j =0; j <= k; j++){
dp[i, j]=-1;}}returnCount(n, k);}privateintCount(int n,int k){if(k ==0)return1;if(n ==1)return0;if(n *(n -1)/2< k)return0;if(n *(n -1)/2== k)return1;if(dp[n, k]!=-1)return dp[n, k];long res =Count(n, k -1);if(k >= n){
res -=Count(n -1, k - n);}
res =(res +Count(n -1, k)+ MOD)% MOD;
dp[n, k]=(int)res;return dp[n, k];}}
funckInversePairs(n int, k int)int{
MOD :=1000000007
dp :=make([][]int, n+1)for i :=range dp {
dp[i]=make([]int, k+1)for j :=range dp[i]{
dp[i][j]=-1}}var count func(n, k int)int
count =func(n, k int)int{if k ==0{return1}if n ==1{return0}if n*(n-1)/2< k {return0}if n*(n-1)/2== k {return1}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)% MOD
dp[n][k]= res
return res
}returncount(n, k)}
class Solution {privateval MOD =1000000007privatelateinitvar dp: Array<IntArray>funkInversePairs(n: Int, k: Int): Int {
dp =Array(n +1){IntArray(k +1){-1}}returncount(n, k)}privatefuncount(n: Int, k: Int): Int {if(k ==0)return1if(n ==1)return0if(n *(n -1)/2< k)return0if(n *(n -1)/2== k)return1if(dp[n][k]!=-1)return dp[n][k]var res =count(n, k -1).toLong()if(k >= n){
res -=count(n -1, k - n)}
res =(res +count(n -1, k)+ MOD)% MOD
dp[n][k]= res.toInt()return dp[n][k]}}
classSolution{letMOD=1000000007var dp:[[Int]]=[]funckInversePairs(_ n:Int,_ k:Int)->Int{
dp =[[Int]](repeating:[Int](repeating:-1, count: k +1), count: n +1)returncount(n, k)}privatefunccount(_ n:Int,_ k:Int)->Int{if k ==0{return1}if n ==1{return0}if n *(n -1)/2< k {return0}if n *(n -1)/2== k {return1}if dp[n][k]!=-1{return dp[n][k]}var res =count(n, k -1)if k >= n {
res -=count(n -1, k - n)}
res =(res +count(n -1, k)+MOD)%MOD
dp[n][k]= res
return res
}}
Time & Space Complexity
Time complexity: O(n∗k)
Space complexity: O(n∗k)
Where n is the size of the permutation and k 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 N from 1 to n:
For each K from 0 to k:
Sum up dp[N - 1][K - pairs] for pairs from 0 to N - 1 where K - pairs >= 0.
Where n is the size of the permutation and k 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 N from 1 to n, and each K from 0 to k:
Start with dp[N][K] = dp[N - 1][K].
If K > 0, add dp[N][K - 1] (cumulative sum from left).
If K >= N, subtract dp[N - 1][K - N] (remove out-of-window term).
classSolution:defkInversePairs(self, n:int, k:int)->int:
MOD =10**9+7
dp =[[0]*(k +1)for _ inrange(n +1)]
dp[0][0]=1for N inrange(1, n +1):for K inrange(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]
classSolution{public:intkInversePairs(int n,int k){constint MOD =1e9+7;
vector<vector<int>>dp(n +1,vector<int>(k +1,0));
dp[0][0]=1;for(int N =1; N <= n; N++){for(int K =0; K <= k; K++){
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];}};
classSolution{/**
* @param {number} n
* @param {number} k
* @return {number}
*/kInversePairs(n, k){constMOD=1e9+7;const dp = Array.from({length: n +1},()=>Array(k +1).fill(0));
dp[0][0]=1;for(letN=1;N<= n;N++){for(letK=0;K<= k;K++){
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];}}
publicclassSolution{publicintKInversePairs(int n,int k){int MOD =1000000007;int[,] dp =newint[n +1, k +1];
dp[0,0]=1;for(int N =1; N <= n; N++){for(int K =0; K <= k; K++){
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];}}
funckInversePairs(n int, k int)int{
MOD :=1000000007
dp :=make([][]int, n+1)for i :=range dp {
dp[i]=make([]int, k+1)}
dp[0][0]=1for N :=1; N <= n; N++{for K :=0; K <= k; K++{
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]}
class Solution {funkInversePairs(n: Int, k: Int): Int {val MOD =1000000007val dp =Array(n +1){IntArray(k +1)}
dp[0][0]=1for(N in1..n){for(K in0..k){
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]}}
Where n is the size of the permutation and k 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 prev array with prev[0] = 1.
For each N from 1 to n:
Create a new cur array and maintain a running total.
classSolution:defkInversePairs(self, n:int, k:int)->int:
MOD =10**9+7
prev =[0]*(k +1)
prev[0]=1for N inrange(1, n +1):
cur =[0]*(k +1)
total =0for K inrange(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]
publicclassSolution{publicintkInversePairs(int n,int k){finalintMOD=1000000007;int[] prev =newint[k +1];
prev[0]=1;for(intN=1;N<= n;N++){int[] cur =newint[k +1];int total =0;for(intK=0;K<= k;K++){
total =(total + prev[K])%MOD;if(K>=N){
total =(total - prev[K-N]+MOD)%MOD;}
cur[K]= total;}
prev = cur;}return prev[k];}}
classSolution{public:intkInversePairs(int n,int k){constint MOD =1e9+7;
vector<int>prev(k +1,0);
prev[0]=1;for(int N =1; N <= n; N++){
vector<int>cur(k +1,0);int total =0;for(int K =0; K <= k; K++){
total =(total + prev[K])% MOD;if(K >= N){
total =(total - prev[K - N]+ MOD)% MOD;}
cur[K]= total;}
prev = cur;}return prev[k];}};
classSolution{/**
* @param {number} n
* @param {number} k
* @return {number}
*/kInversePairs(n, k){constMOD=1e9+7;let prev =newArray(k +1).fill(0);
prev[0]=1;for(letN=1;N<= n;N++){const cur =newArray(k +1).fill(0);let total =0;for(letK=0;K<= k;K++){
total =(total + prev[K])%MOD;if(K>=N){
total =(total - prev[K-N]+MOD)%MOD;}
cur[K]= total;}
prev = cur;}return prev[k];}}
publicclassSolution{publicintKInversePairs(int n,int k){int MOD =1000000007;int[] prev =newint[k +1];
prev[0]=1;for(int N =1; N <= n; N++){int[] cur =newint[k +1];int total =0;for(int K =0; K <= k; K++){
total =(total + prev[K])% MOD;if(K >= N){
total =(total - prev[K - N]+ MOD)% MOD;}
cur[K]= total;}
prev = cur;}return prev[k];}}
funckInversePairs(n int, k int)int{
MOD :=1000000007
prev :=make([]int, k+1)
prev[0]=1for N :=1; N <= n; N++{
cur :=make([]int, k+1)
total :=0for K :=0; K <= k; K++{
total =(total + prev[K])% MOD
if K >= N {
total =(total - prev[K-N]+ MOD)% MOD
}
cur[K]= total
}
prev = cur
}return prev[k]}
class Solution {funkInversePairs(n: Int, k: Int): Int {val MOD =1000000007var prev =IntArray(k +1)
prev[0]=1for(N in1..n){val cur =IntArray(k +1)var total =0for(K in0..k){
total =(total + prev[K])% MOD
if(K >= N){
total =(total - prev[K - N]+ MOD)% MOD
}
cur[K]= total
}
prev = cur
}return prev[k]}}
classSolution{funckInversePairs(_ n:Int,_ k:Int)->Int{letMOD=1000000007var prev =[Int](repeating:0, count: k +1)
prev[0]=1forNin1...n {var cur =[Int](repeating:0, count: k +1)var total =0forKin0...k {
total =(total + prev[K])%MODifK>=N{
total =(total - prev[K-N]+MOD)%MOD}
cur[K]= total
}
prev = cur
}return prev[k]}}
Time & Space Complexity
Time complexity: O(n∗k)
Space complexity: O(k)
Where n is the size of the permutation and k 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.