Easy Problemscountingcombinatoricsgrid

Permutations with K Inversions

An inversion in a permutation of the numbers 1 through n is a pair of positions where a larger value sits before a smaller one. Count how many permutations of 1 through n contain exactly k inversions.

Do this lesson first: climbing stairs

Example input

n = 7, k = 5, so the question is how many orderings of 1 through 7 hold exactly 5 inversions

Expected output

169

Break it down

Answer each question out loud before you open it. Getting it wrong here is the useful part. A revealed answer you never guessed at teaches you nothing.

Fill the table

The table pauses before each cell you have to supply. Type the value the recurrence gives, and the write animation confirms it.

Step not started

Press start. The animation stops at every cell YOUR recurrence must fill.

THE WHOLE OF ROW 0 IS GIVEN, all of it, not merely its first cell. Row 0 is n = 1 and there is no row above it, so nothing in it could be summed from anywhere. dp[0][0] = 1, because the one ordering of a single value has no inversion: an inversion needs two positions to disagree and there is only one position. dp[0][k] = 0 for every k from 1 to the right edge, because one value cannot be out of order with anything. At n = 7 that is 12 given cells, one per declared column. COLUMN 0 IS GIVEN ON EVERY ROW TOO, value 1, and that is a choice with a reason on each side. The value itself is not in doubt: exactly one permutation of any n has no inversions at all, the sorted one. What is in doubt is whether to compute it, and the arithmetic settles that. The window for column 0 is min(0, r) + 1 = 1 cell wide, so the recurrence would copy dp[r - 1][0] across and call it a step, and a one-term sum restates a value rather than deriving one. Saying the sorted permutation out loud says strictly more. At n = 7 that is 6 further given cells, one per row past the first, so 18 of the table's 84 cells are given and the other 66 are summed. THE TABLE IS CUT OFF ON THE RIGHT, AND THE CUT IS A PROOF RATHER THAN A NOTE. A permutation of n values holds at most n(n - 1)/2 inversions, which is 21 at n = 7, so a complete table would need 22 columns; this one declares 12, numbered 0 through 11. Here is why nothing surviving is damaged. Every cell dp[r][k] reads only cells dp[r - 1][k - j] with j at least 0, so every cell it reads sits in its own column or a column to its LEFT. No cell anywhere in this table ever reads a column to its right. Removing columns 12 and beyond therefore removes cells that no remaining cell could ever have consulted, and every cell left on screen holds exactly the value it would hold in the full 22-column table. The answer cell, column 5, is nowhere near the cut. NOW THE HONEST HALF: the rows themselves are incomplete, visibly so at the bottom. At n = 7 five of the seven rows shown are complete, n = 1 through n = 5, whose largest inversion counts are 0, 1, 3, 6 and 10 and all fit inside column 11. The other two are truncated, n = 6 running to 15 and n = 7 to 21. What that costs you is a picture rather than a value: every complete row is symmetric about its middle, 1, 4, 9, 15, 20, 22, 20, 15, 9, 4, 1 at n = 5, because reversing a permutation turns each of its inversions into a non-inversion and back, so k and n(n - 1)/2 - k always tie. A cut row shows that symmetry only up to where it stops. As it happens neither cut hides its row's peak: n = 6's twin 101s sit at columns 7 and 8 and n = 7's twin 573s at columns 10 and 11, both pairs on screen, and what falls off the right edge is only the descent back to a single 1. THE FILL ORDER is row-major, left to right within each row, and the window makes the ordering requirement weaker rather than stronger. Every cell a computed cell reads lies in the row DIRECTLY above it, never in its own row, so finishing each row before starting the next is the whole of what the recurrence needs; within a row the order is free, because no cell in a row depends on another cell in the same row.

row r: holds n = r + 1 values, so row 0 is n = 1
0
1
2
3
4
5
6
7
8
9
10
11
0
1
0
0
0
0
0
0
0
0
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
0
2
1
2
2
1
0
0
0
0
0
0
0
0
3
1
3
5
6
5
3
1
0
0
0
0
0
4
1
4
9
15
20
22
20
15
9
4
1
0
5
1
5
14
29
49
71
90
101
101
90
71
49
6
1
6
20
49
98
169
259
359
455
531
573
573
column k: how many inversions, so the cell counts permutations of r + 1 values with exactly k of them
permutations-with-k-inversions.ts
  1. 1function permutationsWithKInversions(n, k) {
  2. 2 const dp = Array.from({ length: n }, () => new Array(k + 1).fill(0));
  3. 3 dp[0][0] = 1;
  4. 4 for (let r = 1; r < n; r++) {
  5. 5 dp[r][0] = 1;
  6. 6 for (let c = 1; c <= k; c++) {
  7. 7 let total = 0;
  8. 8 for (let j = 0; j <= Math.min(c, r); j++) {
  9. 9 total += dp[r - 1][c - j];
  10. 10 }
  11. 11 dp[r][c] = total;
  12. 12 }
  13. 13 }
  14. 14 return dp[n - 1][k];
  15. 15}
Base caseComputedBeing readAnswer

The code, the trap, the variations

permutations-with-k-inversions.ts
  1. 1function permutationsWithKInversions(n, k) {
  2. 2 const dp = Array.from({ length: n }, () => new Array(k + 1).fill(0));
  3. 3 dp[0][0] = 1;
  4. 4 for (let r = 1; r < n; r++) {
  5. 5 dp[r][0] = 1;
  6. 6 for (let c = 1; c <= k; c++) {
  7. 7 let total = 0;
  8. 8 for (let j = 0; j <= Math.min(c, r); j++) {
  9. 9 total += dp[r - 1][c - j];
  10. 10 }
  11. 11 dp[r][c] = total;
  12. 12 }
  13. 13 }
  14. 14 return dp[n - 1][k];
  15. 15}

Where people go wrong

The window width. There are THREE ways to get the width itself wrong, they fail in completely different registers, and they are worth ranking rather than lumping together; then there is a fourth mistake that is not about the width at all, and it is the one this table protects you from least. TWO OF THE FOUR CAN FAIL SILENTLY, which is the ranking that matters. USING min(k, n) WHERE THE TRUTH IS min(k, n - 1). This grants the largest value one more position than it has, and it is loud rather than subtle: measured, it reports 11, 41, 105 and 224 across n = 4 to 7 against the true 3, 22, 71 and 169, wrong at every size by a factor of 3.667, 1.864, 1.479 and 1.325, and the first cell it gets wrong is dp[1][2], where it claims one ordering of two values holds two inversions. Nothing about it looks fine at small n; it has already failed by the second row. USING min(k, n - 2) IS THE DANGEROUS ONE, and it is dangerous at every size rather than at some of them. It reports 0, 3, 22 and 71 across the same four sizes, and ALL FOUR are the correct answer for the next size down: measured, that variant's bottom row is exactly the true row for n - 1, cell for cell, at every one of n = 4, 5, 6 and 7. The 0 it reports at n = 4 is no exception, because a permutation of 3 values cannot hold 5 inversions, so 0 is the right answer to the question it has quietly substituted. This is not a table that looks broken; it is a table that looks perfect and answers a question one size down. A spot check anywhere lands on a number it has seen before and is reassured by it, which is why the independent check behind this page runs every size the slider offers rather than one. FORGETTING THE min WITH k ALTOGETHER, so j runs to n - 1 and the sum reaches for a negative column. This one is not a wrong-number bug at all, and saying it is would be wrong. Every out-of-range term is a term the true sum did not want, so if reading past the left edge contributes nothing the table is unchanged; in JavaScript dp[r - 1][-1] is undefined instead, so total += undefined poisons that cell to NaN. Measured, the NaN reaches the reported answer at n = 5, 6 and 7 but not at n = 4, where the poisoned cells happen to sit outside the answer's cone. It is a crash-shaped mistake, not a plausible-numbers one. READING THE TWO AXES THE WRONG WAY ROUND is the fourth mistake, the one that is not about the window at all, and the second of the four that can fail silently. This table gives you less protection against it than the triangular pages do. On binomial-coefficient a swapped lookup lands above the diagonal, on a dot, and tells you so at once. Here every cell of the rectangle is live, so a swap lands on a real number: at n = 7 the answer is dp[6][5] = 169 and the transposed cell dp[5][6] = 90, which is a perfectly good count of something else, the orderings of 6 values with 6 inversions.

  • Replace the window sum with a prefix-sum step, dp[r][k] = dp[r][k - 1] + dp[r - 1][k] - dp[r - 1][k - r - 1].

    Two operations per cell instead of a fresh window sum, so the fill drops from O(n k min(k, n)) to O(n k). The identity is just the window telescoping: the window at column k and the window at column k - 1 overlap in all but their two ends, so one add brings in the new right end and one subtract drops the old left end, with the subtracted term omitted when its column is off the table. Nothing about the state, the bases or the fill order moves. What DOES change is the picture, which is why this is a variation rather than the shipped snippet: a cell now reads its own left neighbour as well as the row above, so the arrows stop fanning and the provenance of a value is no longer visible as one step per placement of the largest value. Measured, the saving over this page's four sizes is a factor of 1.389, 1.625, 1.818 and 1.985, so it is real but modest until min(k, n) grows well past what a 12-column table can show.

  • Ask for the number of permutations with AT MOST k inversions rather than exactly k.

    The recurrence, the bases and the sweep are all untouched; only what you read at the end changes, from one highlighted cell to the running total of its row from column 0 to column k. The sweep was already computing every term of that total, which is the generality argument made concrete. This one costs nothing at all, and it is worth being exact about why rather than inventing a price for it. Filling exactly columns 0 through k is what the k = 5 question already required, so the column bound does not move; the rolling-row reduction still applies, since the row being totalled is the row the rolling row holds; and no cell reads a column to its right, so no dependency changes either. The read at the end is the whole of the difference, which makes this the cheapest variation on the page.

  • Count permutations of n with exactly k inversions modulo a prime p, the usual competitive-programming form.

    The recurrence does not change at all: still the same window of the row above. Only the write changes, taking a % p after the sum, which keeps every cell below p. Shape, bases, fill order and the dependency fan are all untouched. The one thing to watch is the prefix-sum variation above, which subtracts: under a modulus that subtraction can go negative, so it needs a + p before the % rather than after it. The plain window sum has no such hazard, which is a small argument for the slower form that this page's cost section does not otherwise make.