Problem overview
The function count_permutations receives n and returns the number of swap permutations of length n. A swap permutation is what you get from (1, 2, ..., n) by repeatedly picking two different items and swapping them, with the rule that each item may take part in at most one swap.
For n = 3 the answer is 4: the original [1, 2, 3] (no swaps), then [1, 3, 2], [2, 1, 3] and [3, 2, 1] (one swap each). A second swap isn't possible, because only one untouched item would remain.
Because every item moves at most once, and a swap moves both of its items, each item ends up either in its place or exchanged with exactly one partner. Mathematicians call these permutations involutions: applying them twice gives back the original order.
Approach: ask what happens to the last item
Listing the permutations and checking each one gets slow quickly, so count with a recurrence. Let dp[i] be the answer for length i, and look at item i, the last one. There are two cases, and they don't overlap:
- Item
iis never swapped. It stays where it is, and the otheri - 1items can be arranged indp[i - 1]ways. - Item
iis swapped with one of the otheri - 1items. There arei - 1choices of partner. Both items are then used up, which leavesi - 2items anddp[i - 2]ways to arrange them.
Adding the cases gives dp[i] = dp[i - 1] + (i - 1) * dp[i - 2]. The base cases are dp[1] = 1 (one item can only stay put) and dp[2] = 2 (stay, or swap once). Checking: dp[3] = dp[2] + 2 * dp[1] = 4, which matches the example, and dp[4] = dp[3] + 3 * dp[2] = 10.
For n = 4 you can confirm the 10 by counting directly: the identity (1 way), one swap (6 ways, one for each pair of positions), and two disjoint swaps (3 ways: positions 1-2 with 3-4, 1-3 with 2-4, and 1-4 with 2-3). The sum is 1 + 6 + 3 = 10.
Step by step
- Convert
ntousizeby shadowing, because Rust indexes vectors withusize. Then createdpas a mutable vector ofn + 1zeros of typei32. Index 0 is never used, so indeximatches lengthi. - Write the base cases. Set
dp[1]to 1. Setdp[2]to 2 only inside anif n >= 2, so the index exists. - Loop
iover3..=n. Each value uses the two before it, which are already filled. - Apply the recurrence. Compute the sum of the two cases, with
icast toi32for the multiplication. - Return
dp[n]as the last expression.
The solution in Rust
impl Solution {
fn count_permutations(n: i32) -> i32 {
let n = n as usize;
let mut dp = vec![0i32; n + 1];
dp[1] = 1;
if n >= 2 {
dp[2] = 2;
}
for i in 3..=n {
dp[i] = dp[i - 1] + (i as i32 - 1) * dp[i - 2];
}
dp[n]
}
}
The Rust details worth knowing:
- Casting precedence. In
i as i32 - 1, the cast binds tighter than the subtraction, so it means(i as i32) - 1. Rust never converts between integer types on its own: multiplying ausizeby ani32is a compile error, so the cast is required. - Why not subtract first in
usize? Hereiis at least 3, soi - 1is safe either way. In general, ausizesubtraction that goes below 0 panics in a debug build and wraps in an optimized one, so casting first, as the lesson does, is the safer habit. - The guard on
dp[2]. Forn = 1the vector has only two cells, indexes 0 and 1, so writing todp[2]would panic with an index out of bounds. Theifprevents that. - Empty ranges. For
n = 1orn = 2the range3..=nis empty and the loop body never runs. That is a normal, valid range in Rust. - The tail expression.
dp[n]has no semicolon, so it is the returned value.
Complexity
The table is filled once, left to right, and each cell reads two earlier cells. No cell is computed twice, which is what makes this fast compared with building every permutation.
Time: O(n). The loop does one constant-size calculation for each length from 3 to n.
Space: O(n). The vector has n + 1 numbers. Only the last two are ever needed, so two variables would reduce this to O(1).
Growth is fast, but it stays inside an i32 for the allowed sizes: for n = 12 the answer is 140152.
Edge cases and common mistakes
- Forgetting the multiplier. Writing
dp[i - 1] + dp[i - 2]counts a single partner. Itemican pair with any ofi - 1items, so the second term needs the factori - 1. That mistake gives 3 forn = 3instead of 4. - Using the wrong index for the second term. After a swap, two items are used, so the rest is
dp[i - 2], notdp[i - 1]. - Writing
dp[2]unconditionally. It panics forn = 1, as described above. - Mixing integer types.
(i - 1) * dp[i - 2]without a cast doesn't compile, becauseiis ausizeanddp[i - 2]ani32. - Counting the identity twice. The identity permutation is one of the answers, and the recurrence includes it through the "never swapped" case all the way down to
dp[1].
Testing
n = 1gives1.n = 2gives2.n = 3gives4.n = 4gives10.n = 5gives26.n = 12gives140152.
