Problem overview
An inversion in a permutation p is a pair of positions (i, j) with i < j and p(i) > p(j): two numbers that are in the wrong order. The sorted permutation [1, 2, 3] has no inversions, and [3, 2, 1] has all three possible ones. The method countPermutations takes n and k and returns how many permutations of the numbers 1 to n have exactly k inversions. The lesson's notes limit n to 12 and k to the largest possible inversion count, n * (n - 1) / 2.
For n = 4 and k = 2 the answer is 5, and the permutations are [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 4, 3] and [3, 1, 2, 4]. Check one: in [1, 4, 2, 3] the inverted pairs are (4, 2) and (4, 3), so there are exactly 2.
Approach: build longer permutations from shorter ones
Generating all n! permutations and counting inversions in each works for tiny n only: 12! is 479,001,600 permutations, each needing an inversion count. Dynamic programming avoids this by counting without listing.
The key is to grow a permutation by inserting the largest number. Take any permutation of the numbers 1 to i and insert the new number i + 1 somewhere. The new number is bigger than everything else, so it forms an inversion with every element that ends up to its right, and with none to its left. Put it at the end: 0 new inversions. One place to the left: 1 new inversion. At the very front: i new inversions. So the i + 1 insertion positions add exactly 0, 1, 2, ..., i inversions, one position for each amount.
That gives a recurrence. Let dp[i][j] be the number of permutations of length i with exactly j inversions. Every such permutation produces i + 1 longer ones, with j, j + 1, ..., j + i inversions. Different permutations or different positions never produce the same longer permutation, because removing the largest number recovers the original permutation and the position. The counts can therefore be added without worrying about double counting.
Here is the table for n = 4 and k = 2, with columns for 0, 1 and 2 inversions:
| Length | 0 inversions | 1 inversion | 2 inversions |
|---|---|---|---|
| 1 | 1 | 0 | 0 |
| 2 | 1 | 1 | 0 |
| 3 | 1 | 2 | 2 |
| 4 | 1 | 3 | 5 |
The bottom-right cell is the answer, 5. Length 3 has 6 permutations in total, split 1, 2, 2 and 1 across 0 to 3 inversions. The column for 3 inversions is not shown, because the table stops at k = 2.
Step by step
- Create the table. Declare
dpasnew int[n + 1][k + 1]: rows for lengths 0 tonand columns for 0 tokinversions. Java fills a newintarray with zeros, so no initialisation loop is needed. - Set the base case.
dp[1][0] = 1. The only permutation of length 1 has no inversions. - Loop over lengths. The index
iruns from 1 ton - 1. Each pass builds rowi + 1from rowi. The final pass fills rown. - Loop over inversion counts. The index
jruns from 0 tokand selects the celldp[i][j]to push forward. - Loop over the new inversion counts. The index
j1runs fromjtoMath.min(j + i, k). These are the inversion counts the extended permutations can have. Counts aboveknever contribute to the answer, so the range is capped atk. - Add.
dp[i + 1][j1] += dp[i][j]. Every permutation counted indp[i][j]yields exactly one permutation of lengthi + 1withj1inversions. - Return
dp[n][k].
The solution in Java
class Solution {
public int countPermutations(int n, int k) {
int[][] dp = new int[n + 1][k + 1];
dp[1][0] = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j <= k; j++) {
for (int j1 = j; j1 <= Math.min(j + i, k); ++j1) {
dp[i + 1][j1] += dp[i][j];
}
}
}
return dp[n][k];
}
}
Java details worth noticing:
- A two-dimensional array is an array of arrays.
new int[n + 1][k + 1]allocates all the rows at once, and every cell starts at 0. - The loop bound is recomputed.
Math.min(j + i, k)in the loop condition is evaluated on every pass of the innermost loop. That is cheap, and it keeps the cap in one place. - The pre-increment
++j1behaves the same asj1++in a loop header, because the value of the expression is not used. intis large enough. A table entry never exceeds the number of permutations of its length, and withnup to 12 that is at most 12! = 479,001,600, which fits in anint(maximum 2,147,483,647). For much largernthe counts would overflow silently, and alongor a result modulo a prime would be needed.
Complexity
The three loops run about n times, k + 1 times and up to i + 1 times, so the time is O(n^2 * k). The table has (n + 1) * (k + 1) cells, so the space is O(n * k). Since only row i is read when row i + 1 is built, two rows would be enough to cut the memory to O(k). A prefix-sum version of the inner loop also reduces the time to O(n * k).
Edge cases and common mistakes
k = 0. Exactly one permutation, the sorted one, has no inversions, socountPermutations(3, 0)is 1.- The maximum
k. Only the reversed permutation reachesn * (n - 1) / 2inversions. Forn = 4andk = 6the result is 1. kabove the maximum. No permutation has that many inversions, and the method returns 0. Forn = 4andk = 7the table simply stays zero in that cell.n = 1. The loop does not run and the method returnsdp[1][0], which is 1 fork = 0.n = 0. The table has only one row, sodp[1][0] = 1throwsArrayIndexOutOfBoundsException. The lesson's inputs start at length 1.- Stopping at the wrong row. The loop ends at
i < nbecause each pass writes rowi + 1. Looping toi <= nwould write outside the table.
Testing
Try (4, 2) (5), (3, 1) (2), (3, 3) (1) and (5, 2) (9).
