Problem overview
The method countPermutations(n) counts the permutations of 1 to n that can be reached from (1, 2, ..., n) by a series of swaps, where each item takes part in at most one swap. Zero swaps is allowed, so the identity permutation counts. The notes limit n to the range 3 to 12.
For n = 3 the answer is 4: [1, 2, 3] with no swaps, and the three single swaps [1, 3, 2], [2, 1, 3] and [3, 2, 1]. Two swaps would need four different items, and a third item would have to be swapped twice, so nothing more is possible.
The rule "each item is swapped at most once" means the swaps never overlap. A swap permutation is a set of disjoint pairs that trade places, while all the other items stay where they are.
Approach: ask what happens to the last item
Let dp(i) be the number of swap permutations of length i. Look at item i, the last one:
- It is never swapped. It stays in place and the other
i - 1items are arranged in any valid way:dp(i - 1)possibilities. - It is swapped with another item. There are
i - 1choices for the partner. Both items are then used up, and the remainingi - 2items are arranged indp(i - 2)ways. That gives(i - 1) * dp(i - 2)possibilities.
The two cases cover every permutation exactly once, so dp(i) = dp(i - 1) + (i - 1) * dp(i - 2). The recurrence needs two starting values. One item can only stay in place, so dp(1) = 1. Two items can stay or swap, so dp(2) = 2.
The table grows as follows:
n: 1 2 3 4 5 6 7
dp(n): 1 2 4 10 26 76 232
For example, dp(4) = dp(3) + 3 * dp(2) = 4 + 6 = 10. These counts are known as the involution numbers: the permutations that are their own inverse, which is exactly what a set of disjoint swaps produces.
Step by step
- Create the array with
Array.fill(n + 1)(0). Index 0 is unused, so indexiholds the count for lengthi. - Set the base cases
dp(1) = 1anddp(2) = 2. The lesson writes both on one line, separated by a semicolon. - Loop over
3 to n. Starting at 3 is safe because the two values it needs,dp(1)anddp(2), are already set. - Apply the recurrence to fill
dp(i). - End the method with
dp(n).
The solution in Scala
class Solution {
def countPermutations(n: Int): Int = {
val dp = Array.fill(n + 1)(0)
dp(1) = 1; dp(2) = 2
for (i <- 3 to n) {
dp(i) = dp(i - 1) + (i - 1) * dp(i - 2)
}
dp(n)
}
}
Some Scala details in this code:
Array.fill(n + 1)(0)builds anArray[Int]ofn + 1cells, each set to 0. The size goes in the first parentheses and the value in the second.- A semicolon can separate two statements on one line. Scala normally ends a statement at the line break, but
dp(1) = 1; dp(2) = 2is valid because of the semicolon. valand the array's contents.val dpmeans the name can't be pointed at another array, but the cells can still be changed, which is whydp(1) = 1works.- Ranges.
3 to nincludesn. Whennis 3 the loop runs once, and a range such as3 to 2is empty. - No
returnneeded. The last expression of the method body,dp(n), is its value.
Complexity
Time: O(n). One loop, with constant work per iteration.
Space: O(n). The array has n + 1 cells. Since each value needs only the previous two, two variables would be enough.
Edge cases and common mistakes
- Counting the identity. Zero swaps is a valid swap permutation. That is why
dp(2)is 2 and not 1. - Multiplying by
iinstead ofi - 1. The partner for itemican be any of the otheri - 1items. Usingiwould let an item swap with itself, anddp(3)would come out as 5 instead of 4. - Using
dp(i - 1)twice. After a swap, two items are used, so the rest hasi - 2items, noti - 1. - Small
n. The notes promisen >= 3. Withn = 2the method returns 2, but withn = 1the array has only two cells, sodp(2) = 2throws anArrayIndexOutOfBoundsException. - Overflow.
Intwraps silently, but the largest answer in range,countPermutations(12), is 140152. There is no overflow risk at that size. - Starting the loop at 2. It would overwrite
dp(2)withdp(1) + 1 * dp(0), which is 1, because index 0 is unused and holds 0.
Testing
n = 3gives4.n = 4gives10.n = 5gives26.n = 8gives764.n = 12gives140152.
