Problem overview
The function count_permutations receives n and k and returns how many permutations of the numbers 1 to n have exactly k inversions. An inversion is a pair of positions i < j where the value at i is larger than the value at j: a pair that is out of order.
For n = 4 and k = 2 the answer is 5. The five permutations are [1, 3, 4, 2] (out-of-order pairs 3 and 2, 4 and 2), [1, 4, 2, 3], [2, 1, 4, 3], [2, 3, 1, 4] and [3, 1, 2, 4]. Each has exactly two pairs that are out of order.
Approach: insert the largest value
Trying all n! permutations and counting inversions in each is far too slow as n grows. Instead, build permutations by growing them. Take a permutation of the values 1 to i and insert the new largest value, i + 1, at some position. The new value is bigger than everything before it, so it forms an inversion with every element that ends up to its right, and with nothing to its left.
That gives a neat rule. Put the new value at the very end and it adds 0 inversions. Put it one place from the end and it adds 1, and so on, up to i new inversions when it goes to the very front. So each permutation of length i with j inversions produces exactly i + 1 permutations of length i + 1, with j, j + 1, ..., j + i inversions, one each. Every permutation of length i + 1 arises this way exactly once, because removing the largest value gives back the smaller permutation.
Here is the idea at work. Row 2 is [1, 1]: the permutations [1, 2] and [2, 1] have 0 and 1 inversions. Each of them gets the value 3 inserted in one of three slots, which adds 0, 1 or 2 inversions. The one with 0 inversions spreads to counts 0, 1 and 2; the one with 1 spreads to counts 1, 2 and 3. Adding up, row 3 is [1, 2, 2, 1]: six permutations in total, as expected for 3!.
So let dp[i][j] be the number of permutations of length i with exactly j inversions, and push each count forward into the range j to j + i of the next row.
Step by step
- Convert the inputs to
usize. Vectors are indexed withusize, sonandkare shadowed byusizecopies. - Create the table. It has
n + 1rows ofk + 1zeros each, asi32. Rowiis for permutations of lengthi. - Seed length 1. The only permutation of one element has no inversions, so
dp[1][0]is 1. - Loop over lengths
ifrom 1 ton - 1. Each pass builds rowi + 1. - Loop over inversion counts
jfrom 0 tok. - Skip empty cells. A zero has nothing to pass on. This only saves work; the result is the same without it.
- Loop over the new counts
j1, fromjup tomin(j + i, k). Counts abovekcan never lead back down, so the range is capped atk. - Add the count forward:
dp[i + 1][j1] += dp[i][j]. - Return
dp[n][k].
The solution in Rust
impl Solution {
fn count_permutations(n: i32, k: i32) -> i32 {
let n = n as usize;
let k = k as usize;
let mut dp = vec![vec![0i32; k + 1]; n + 1];
dp[1][0] = 1;
for i in 1..n {
for j in 0..=k {
if dp[i][j] == 0 {
continue;
}
for j1 in j..=std::cmp::min(j + i, k) {
dp[i + 1][j1] += dp[i][j];
}
}
}
dp[n][k]
}
}
The Rust details worth knowing:
- Shadowing.
let n = n as usize;declares a newnthat hides thei32parameter for the rest of the function. The old value is not changed; the name just refers to the new one. - The
0i32suffix. It fixes the cell type toi32, the return type, so no later line needs a cast. as usizeon a negative number. Casting a negativei32tousizegives an enormous value, not an error. The lesson's inputs are non-negative, so this doesn't arise here.continue. It ends the current pass of the innermost loop only, here thejloop, and moves on to the nextj.- Inclusive ranges.
j..=std::cmp::min(j + i, k)includes its upper end.std::cmp::mintakes two values of the same type, and both areusizehere. - Overflow. With
nat most 12, every cell is at most 12! = 479001600, the number of all permutations, which fits in ani32. In an optimized build an overflow would wrap silently, but here none can happen.
Complexity
Time: O(n * k * n), that is O(n²k). There are n lengths and k + 1 counts, and the innermost loop runs at most i + 1 times, which is at most n.
Space: O(n * k). The table has (n + 1) * (k + 1) cells.
Edge cases and common mistakes
- Zero inversions.
k = 0gives 1 for everyn: only the sorted permutation. - Maximum inversions.
n = 4,k = 6gives 1: only the fully reversed permutation. Akaboven * (n - 1) / 2gives 0, for examplen = 4,k = 7. - Single element.
n = 1,k = 0gives 1. The loop1..1is empty anddp[1][0]is returned. - Forgetting the cap. Without
min(..., k),dp[i + 1][j1]can index past the end of the row and panic. - Wrong insertion count. A permutation of length
ihasi + 1insertion slots, giving 0 toinew inversions. Usingj + i - 1as the top end misses the front slot.
Testing
n = 4,k = 2gives5.n = 3,k = 0gives1.n = 4,k = 6gives1.n = 4,k = 7gives0.n = 5,k = 4gives20.n = 12,k = 66gives1.
