Start learning free

LESSON 341 OF 409 · DYNAMIC PROGRAMMING

Permutations with K Inversions in C++ (Time Complexity: O(N*K))

Practice O(n * k) Read the full explanation

Given two non-negative integers N and K, return the number of permutations of length N with exactly K inversions

An inversion is a pair of indices (i, j) with i < j and p(i) > p(j)

Example:

Input: N = 4, K = 2
Output: 5
Explanation: [
    [1, 3, 4, 2],
    [1, 4, 2, 3],
    [2, 1, 4, 3],
    [2, 3, 1, 4],
    [3, 1, 2, 4],
]
Notes:
N <= 12
K <= N * (N - 1) / 2
STEP 1 OF 7
    Ctrl + Enter to check

    C++ · DYNAMIC PROGRAMMING

    Permutations With K Inversions in C++, explained

    Understanding the Problem

    The core challenge of this problem is to count the number of permutations of length N that have exactly K inversions. An inversion is defined as a pair of indices (i, j) such that i < j and p(i) > p(j). This problem is significant in combinatorial mathematics and has applications in sorting algorithms and sequence analysis.

    Potential pitfalls include misunderstanding the definition of inversions and not accounting for all possible permutations.

    Approach

    To solve this problem, we can use dynamic programming. The idea is to build a table where dp[n][k] represents the number of permutations of length n with exactly k inversions.

    We start with a naive approach of generating all permutations and counting inversions, but this is not feasible for larger values of N due to its factorial time complexity.

    Instead, we use a dynamic programming approach to optimize the solution:

    • Initialize a 2D array dp where dp[i][j] represents the number of permutations of length i with exactly j inversions.
    • Base case: dp[0][0] = 1 (an empty permutation has zero inversions).
    • Transition: For each length i and each number of inversions j, calculate dp[i][j] by considering the position of the last element in the permutation.

    Algorithm

    Here is a step-by-step breakdown of the dynamic programming algorithm:

    1. Initialize a 2D array dp with dimensions (N+1) x (K+1) and set all values to 0.
    2. Set dp[0][0] = 1 as the base case.
    3. For each length i from 1 to N:
      • For each number of inversions j from 0 to K:
        • For each position p from 0 to i-1:
          • Update dp[i][j] by adding dp[i-1][j-p] if j-p >= 0.
    4. The result is dp[N][K].

    Code Implementation

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    // Function to count permutations with exactly K inversions
    int countPermutationsWithKInversions(int N, int K) {
        // Initialize a 2D dp array with dimensions (N+1) x (K+1)
        vector<vector<int>> dp(N + 1, vector<int>(K + 1, 0));
        
        // Base case: An empty permutation has zero inversions
        dp[0][0] = 1;
        
        // Fill the dp table
        for (int n = 1; n <= N; ++n) {
            for (int k = 0; k <= K; ++k) {
                for (int p = 0; p < n; ++p) {
                    if (k - p >= 0) {
                        dp[n][k] += dp[n - 1][k - p];
                    }
                }
            }
        }
        
        // The result is the number of permutations of length N with exactly K inversions
        return dp[N][K];
    }
    
    int main() {
        int N = 4, K = 2;
        cout << "Number of permutations of length " << N << " with exactly " << K << " inversions: " << countPermutationsWithKInversions(N, K) << endl;
        return 0;
    }
    

    Complexity Analysis

    The time complexity of this dynamic programming approach is O(N*K) because we fill a table of size (N+1) x (K+1) and each cell requires O(N) operations. The space complexity is also O(N*K) due to the storage of the dp table.

    Edge Cases

    Potential edge cases include:

    • N = 0 or K = 0: The function should handle these gracefully, returning 1 for dp[0][0] and 0 for other cases.
    • K > N*(N-1)/2: This is impossible, so the function should return 0.

    Testing

    To test the solution comprehensively, consider a variety of test cases:

    • Simple cases: N = 1, K = 0; N = 2, K = 1.
    • Edge cases: N = 0, K = 0; N = 5, K = 0; N = 5, K = 10.
    • Complex cases: N = 10, K = 20.

    Use a testing framework like Google Test for automated testing.

    Thinking and Problem-Solving Tips

    When approaching such problems:

    • Break down the problem into smaller subproblems.
    • Consider using dynamic programming to optimize recursive solutions.
    • Practice similar problems to improve your problem-solving skills.

    Conclusion

    Understanding and solving problems involving permutations and inversions is crucial for mastering combinatorial algorithms. Practice and explore further to deepen your understanding.

    Additional Resources

    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free