Problem overview
The function countPermutations takes an int n and returns how many permutations of length n exist: the ways to arrange the numbers 1 to n in a row, using each exactly once. For n = 3 there are 6: [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2] and [3, 2, 1]. The lesson's notes say n is at most 12.
Approach: choose the first element, then arrange the rest
To build a permutation of length i, pick its first element. There are i choices. Once it is placed, the remaining i - 1 positions are filled with the remaining i - 1 numbers, and the number of ways to do that is the same question for a smaller size. So the count for i is i times the count for i - 1.
This is the factorial recurrence, i! = i * (i - 1)!, written as a DP table. The base case is length 0. There is exactly one way to arrange nothing, the empty permutation, so dp[0] = 1. From there dp[1] = 1 * 1 = 1, dp[2] = 2 * 1 = 2, dp[3] = 3 * 2 = 6, and so on.
Step by step
- Create the table with
make([]int, n+1), so indexnexists. Every entry starts at 0. - Set
dp[0] = 1. Starting at length 0 instead of 1 also coversn = 0, where the table has only index 0. - Loop with
ifrom 1 whilei <= n, so entries are filled in increasing order anddp[i-1]is always ready. - Compute
dp[i] = i * dp[i-1]:ichoices for the first element times the arrangements of the otheri - 1. - Return
dp[n].
The solution in Go
func countPermutations(n int) int {
dp := make([]int, n+1)
dp[0] = 1
for i := 1; i <= n; i++ {
dp[i] = i * dp[i-1]
}
return dp[n]
}
The Go details worth a closer look:
- Slices from
make.make([]int, n+1)gives a slice of lengthn + 1filled with zeros. A fixed-size array would not do, because the sizenis only known when the function runs. - The loop. Go's only loop keyword is
for. The clausei := 1; i <= n; i++needs no parentheses, andiexists only inside the loop. - No overflow at this size.
intis 64 bits in Go 1.22 on 64-bit systems, and the largest value here,12!, is 479001600. Go'sintkeeps wrapping silently when it overflows, so the table would go wrong without any error fornaround 21 and above, since21!no longer fits in 64 bits. The lesson's limit of 12 is far from that. - Mixing types.
i * dp[i-1]multiplies twointvalues. Go does no implicit conversion between numeric types, so mixing in anint64orfloat64would need an explicit conversion.
What the table looks like
For n = 5 the loop fills the table like this: dp starts as [1, 0, 0, 0, 0, 0]. After the iteration for i = 1 it is [1, 1, 0, 0, 0, 0], then i = 2 gives 2, i = 3 gives 6, i = 4 gives 24 and i = 5 gives 120. The finished table is [1, 1, 2, 6, 24, 120], and the function returns its last entry. Every number was computed once and then reused by the next step, which is the saving that dynamic programming brings: the larger answer never recomputes the smaller ones.
The statement lists the permutations, but the function only counts them. Producing all of them takes time proportional to n * n!, because each permutation has n numbers, while counting needs only n multiplications. That difference is why the recurrence matters: the count is cheap even when the list would be far too long to write out.
Complexity
Time: O(n), one multiplication per step. Space: O(n) for the table. Each entry depends only on the one before it, so a single running product would use O(1) space.
Edge cases and common mistakes
n = 0. The loop does not run, and the function returnsdp[0], which is 1: the empty permutation counts.n = 1.dp[1] = 1 * dp[0] = 1.- Starting
dp[0]at 0. Every entry is a multiple of the previous one, so the whole table stays 0. - Using
i < n. The loop stops one entry early, anddp[n]remains 0. - Multiplying by
i-1. That counts the arrangements with one fewer choice for the first element. The first position hasicandidates.
Testing
n = 0gives1.n = 3gives6.n = 5gives120.n = 12gives479001600.