Problem overview
The method CountPermutations(int n) counts the permutations of 1 to n that you can build from [1, 2, ..., n] by repeatedly swapping two items, with one rule: each item may be swapped at most once. Doing no swaps is allowed, so the starting permutation counts.
For n = 3 the answer is 4: the original [1, 2, 3], and one swap each of the pairs (2, 3), (1, 2) and (1, 3), giving [1, 3, 2], [2, 1, 3] and [3, 2, 1]. A second swap would need two unused items, and after one swap only one item is left. Larger inputs allow several independent swaps, for example [2, 1, 4, 3] for n = 4.
Approach: decide what happens to the last item
Since each item moves at most once, and a swap moves both of its items, the result is a set of disjoint pairs that trade places while every other item stays where it is. Counting those arrangements is a classic recurrence.
Look at item i, the last of the first i items. It is in one of two situations:
- It is never swapped. Then it stays put, and the other
i - 1items can be arranged indp[i - 1]ways. - It is swapped with one of the other
i - 1items. There arei - 1choices of partner. Both are now used up, so the remainingi - 2items can be arranged indp[i - 2]ways.
The two cases do not overlap, so dp[i] = dp[i - 1] + (i - 1) * dp[i - 2]. These numbers also count the permutations that are their own inverse, which are called involutions.
Worked from the base cases: one item can only stay, so dp[1] = 1. Two items can stay or swap, so dp[2] = 2. Then dp[3] = 2 + 2 * 1 = 4, matching the example, and dp[4] = 4 + 3 * 2 = 10.
Step by step
- Create the table. An
int[]of lengthn + 1letsdp[i]be indexed directly byi. Index 0 is never used. C# zero-fills the array. - Set the base cases
dp[1] = 1anddp[2] = 2, which the recurrence cannot produce on its own. - Loop from 3 to
ninclusive. Each value needs the two before it, and they are ready by then. - Apply the recurrence in each iteration, as derived above.
- Return
dp[n].
The solution in C#
class Solution {
public int CountPermutations(int n) {
int[] dp = new int[n + 1];
dp[1] = 1; dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + (i - 1) * dp[i - 2];
}
return dp[n];
}
}
A few C# notes:
- Two statements on one line.
dp[1] = 1; dp[2] = 2;is legal, since C# separates statements by semicolons, not line breaks. - Array size and bounds.
new int[n + 1]has valid indexes 0 ton. The code assignsdp[2]directly, sonmust be at least 2, or the array is too short and C# throwsIndexOutOfRangeException. The lesson's range starts at 3. - Overflow stays away. The values grow fast:
dp[12]is 140,152 and still tiny next toint.MaxValue. For much largern,intarithmetic would wrap silently instead of failing.
Complexity
Time: O(n). One pass with constant work per step.
Space: O(n). The table has n + 1 entries. Each value uses only the previous two, so two variables would also work, and the space would drop to O(1).
Edge cases and common mistakes
- Forgetting the multiplier. Writing
dp[i - 1] + dp[i - 2]gives Fibonacci numbers. Itemihasi - 1possible partners, sodp[i - 2]must be multiplied byi - 1. - Counting the original arrangement. The identity permutation counts as one. That is why
dp[1]is 1 and not 0. - Counting swaps in different orders separately. Swapping (1, 2) then (3, 4) gives the same result as (3, 4) then (1, 2). The recurrence counts resulting permutations, not sequences of operations.
- Starting the loop too early. With
i = 2, the recurrence would readdp[0], which is 0, and overwrite the correct base case.
Testing
n = 3gives 4.n = 4gives 10.n = 5gives 26.n = 12gives 140152.