Problem overview
The method countPermutations(n) returns how many permutations of length n can be reached from (1, 2, ..., n) by swapping pairs of items, where no item may be swapped more than once. Applying zero swaps is allowed, so the identity permutation counts.
For n = 3 the answer is 4: the identity [1, 2, 3], plus the three single swaps [1, 3, 2], [2, 1, 3] and [3, 2, 1]. A second swap is impossible, because after one swap only one item is still untouched, and a swap needs two. For larger n several swaps can be chained as long as they use different items. The notes keep n between 3 and 12.
Approach: decide what happens to the last item
Because every item is swapped at most once, the final arrangement is made of disjoint pairs that traded places, and all other items stayed where they were. So the question is how many ways there are to pick such pairs among n items.
Let dp[i] be the answer for i items and look at item i:
- It is never swapped. It stays in place, and the other
i - 1items are arranged indp[i-1]ways. - It is swapped with one of the other
i - 1items. There arei - 1choices of partner. Both items are then used up, so the remainingi - 2items are arranged indp[i-2]ways.
These two cases don't overlap, and together they cover every possibility, so dp[i] = dp[i-1] + (i-1) * dp[i-2]. The base cases are dp[1] = 1 (one item can only stay) and dp[2] = 2 (stay, or swap once).
The values grow like this:
n: 1 2 3 4 5 6
answer: 1 2 4 10 26 76
Check n = 3: dp[3] = dp[2] + 2 * dp[1] = 2 + 2 = 4, matching the example. Check n = 4: dp[4] = 4 + 3 * 2 = 10.
Step by step
- Create the slice
dpwithmake([]int, n+1). It has one more cell than needed so thatdp[i]is the answer foriitems; cell 0 is unused. - Set the base cases with a single tuple assignment:
dp[1], dp[2] = 1, 2. - Loop from 3 to
ninclusive. Each value uses the two before it, which are already computed. - Apply the recurrence to
dp[i]. - Return
dp[n].
The solution in Go
type Solution struct{}
func (Solution) countPermutations(n int) int {
dp := make([]int, n+1)
dp[1], dp[2] = 1, 2
for i := 3; i <= n; i++ {
dp[i] = dp[i-1] + (i-1)*dp[i-2]
}
return dp[n]
}
Go details worth a closer look:
- The receiver has no name.
(Solution)declares the method on the type without binding a variable, which is legal when the body doesn't use the receiver. - Tuple assignment.
dp[1], dp[2] = 1, 2evaluates the right side first and then assigns. The same form swaps two variables without a temporary:a, b = b, a. make([]int, n+1)returns a slice ofn + 1zeros. Indexing past the end is not an overflow but a run-time panic.- Loop style. Go has only
for. The three-part form here is the usual counting loop, and the variableiexists only inside it. - Integer size.
intis 64 bits here. The answer forn = 12is 140,152, and values stay well inside the range for much largern. A silent wrap would only appear far beyond the lesson's limits.
Complexity
One loop of about n steps with constant work in each gives O(n) time. The slice holds n + 1 integers, so the space is O(n). Since each value needs only the previous two, two variables could replace the slice and bring the space down to O(1).
Edge cases and common mistakes
nbelow 2. Withn = 1the slice has two cells, sodp[2] = 2is out of range and the program panics. The lesson's notes start atn = 3, so this isn't tested, but a general-purpose version would return early for smalln.- Forgetting the factor
i - 1. Itemican pick any ofi - 1partners, sodp[i-1] + dp[i-2]would count only one of them. Forn = 4it would give 6 instead of 10. - Counting ordered sequences of swaps. Swapping (1, 2) and then (3, 4) gives the same permutation as the other order. The recurrence counts resulting permutations, not sequences of operations.
- Confusing this with all permutations. For
n = 4there are 24 permutations but only 10 swap permutations. For example, the cycle[2, 3, 1, 4]is not reachable without moving an item twice.
Testing
The values for n from 3 to 6 are 4, 10, 26 and 76, and n = 12 gives 140152. These numbers also count the permutations that are their own inverse, which is a handy way to cross-check the idea.