Start learning free

LESSON 357 OF 540 · BACKTRACKING

Permutations in Go: Backtracking with a used Slice, O(n · n!)

Practice O(n · n!) Read the full explanation

Given an array nums of distinct integers, return every permutation of nums: every way to put all of its numbers in a row, each number exactly once.

Return the permutations in the order you get by filling the positions from first to last and always trying the numbers in their order in nums: every permutation that starts with nums[0] comes first, then every one that starts with nums[1], and so on. Among the permutations with the same first number, the second number decides the same way, trying the numbers that are left in their order in nums; then the third, and so on. So nums itself always comes first, and nums reversed comes last.

 

Example 1:

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Explanation: Two permutations start with 1, then two with 2, then two with 3.
After 1, the numbers left are 2 and 3, tried in that order, so [1,2,3] comes before [1,3,2].

Example 2:

Input: nums = [3,1,2]
Output: [[3,1,2],[3,2,1],[1,3,2],[1,2,3],[2,3,1],[2,1,3]]
Explanation: The numbers are tried in their order in nums, 3 then 1 then 2, not in ascending order.
After 1, the numbers left are 3 and 2, in that order in nums, so [1,3,2] comes before [1,2,3].

Example 3:

Input: nums = [1]
Output: [[1]]
Explanation: One number can be put in a row in only one way.

 

Constraints:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10
  • All the numbers in nums are distinct.

Note:

The tutorial uses backtracking. Build a permutation one position at a time in a list current, and keep an array used of true/false values, one per number, that says which numbers current already holds. For the next position, go through the numbers in order and skip the used ones; for each unused number, take it (mark it used and add it to current), fill the remaining positions with a recursive call, then give it back (remove it and mark it unused) so the next number can take its place. When current holds all n numbers, save a copy of it. Trying the numbers in their order in nums gives exactly the order above.

There are n! permutations. Copying each one takes n steps, and the calls that build them, fewer than e · n! in all, each loop over the n numbers, so the time is O(n · n!). Besides the answer, the extra space is O(n): current, used and one call per position.

STEP 1 OF 14
    Ctrl + Enter to check

    GO · BACKTRACKING

    Permutations in Go, explained

    Problem overview

    The function permute(nums []int) [][]int receives a slice of distinct integers and returns every permutation: every way to put all the numbers in a row, each used exactly once.

    The order of the answer is fixed. The positions are filled from first to last, and the numbers are always tried in their order in nums. Every permutation that starts with nums[0] comes first, then every one that starts with nums[1], and so on; among permutations with the same first number, the second number decides the same way. So nums itself comes first and nums reversed comes last.

    For nums = [1, 2, 3] the answer is [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]. For nums = [3, 1, 2] it is [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]]. The numbers are tried in the order 3, 1, 2, not in ascending order.

    Approach: fill one position at a time

    With n numbers there are n! permutations: 6 for three numbers, 40,320 for eight. Whatever the method, the answer is that big, so the aim is to produce each permutation once without wasted work.

    Backtracking builds a permutation position by position in a slice current, and keeps a slice of booleans, used, that says which numbers current already holds:

    • When current has as many numbers as nums, it is a whole permutation: save a copy of it.
    • Otherwise, go through the numbers from index 0 and skip the used ones. For each free number, take it (mark it used, append it to current), recurse to fill the remaining positions, then give it back (remove it, mark it unused) so the next number can take its place.

    Because the loop always starts at index 0 and moves up, the numbers are tried in their order in nums, which gives exactly the order the statement asks for.

    Step by step

    1. Create result as an empty [][]int, written [][]int{}. Each element is a permutation.
    2. Create used with make([]bool, len(nums)). used[i] says whether nums[i] is already in the permutation being built. make sets every element to false, the zero value of a bool.
    3. Start the search with backtrack(nums, used, []int{}, &result). No position is filled, so the permutation so far is empty. The last argument is the address of result, because the helper has to store the grown slice that append returns.
    4. Return result after the call.
    5. Declare the helper backtrack(nums []int, used []bool, current []int, result *[][]int), returning nothing.
    6. Base case. If len(current) == len(nums), current holds every number once.
    7. Save a copy with *result = append(*result, append([]int{}, current...)) and return.
    8. Loop over the candidates with for i := 0; i < len(nums); i++. Always starting at 0 is what produces the required order.
    9. Skip used numbers with if used[i] { continue }. A number already in current can't appear twice.
    10. Mark it: set used[i] to true, so the deeper calls won't take it.
    11. Append it: current = append(current, nums[i]) fills the next position.
    12. Recurse with backtrack(nums, used, current, result) to fill the rest. result is already a pointer, so it is passed on as it is.
    13. Remove it: current = current[:len(current)-1] drops the last number.
    14. Unmark it: set used[i] back to false, so later permutations can use nums[i] in another position.

    The solution in Go

    func permute(nums []int) [][]int {
    	result := [][]int{}
    	used := make([]bool, len(nums))
    	backtrack(nums, used, []int{}, &result)
    	return result
    }
    
    func backtrack(nums []int, used []bool, current []int, result *[][]int) {
    	if len(current) == len(nums) {
    		*result = append(*result, append([]int{}, current...))
    		return
    	}
    	for i := 0; i < len(nums); i++ {
    		if used[i] {
    			continue
    		}
    		used[i] = true
    		current = append(current, nums[i])
    		backtrack(nums, used, current, result)
    		current = current[:len(current)-1]
    		used[i] = false
    	}
    }

    The Go details worth a closer look:

    • Slice headers and shared arrays. A slice value is a small header (pointer, length, capacity). used is passed as a header too, but both headers point to the same array, so used[i] = true in a deep call is seen by every caller. That is what lets the marks work. result is different: append may return a header for a new array, so it travels as a pointer, *[][]int.
    • Why the copy matters. append([]int{}, current...) builds a slice with its own array. Saving current itself stores a slice that may share memory with later calls. For [1, 2, 3] that happens to work, but for [1, 2, 3, 4] the output has 24 entries where [1 2 4 3] appears twice and [1 2 3 4] never does.
    • The undo line. Inside one call, current grows by one number on each turn of the loop. Trimming it back to its old length before the next turn is what lets the next candidate take the same position.
    • The classic loop. Go's for i := 0; i < len(nums); i++ has an init statement, a condition and a post statement, with no parentheses.
    • continue. It ends the current turn of the loop and goes on with the next i.
    • make versus a literal. make([]bool, len(nums)) makes a slice whose length is only known at run time, with every element false.

    Complexity

    Time: O(n · n!). There are n! permutations, and saving each one copies n numbers. The calls that build them are fewer than e · n! in all, and each call loops over the n numbers, which stays within the same bound.

    Space: O(n) besides the answer. current and used hold at most n items and the recursion is at most n + 1 calls deep. The answer itself holds n · n! numbers, but that is the output.

    Edge cases and common mistakes

    • One number. [1] gives [[1]]: a single number can be arranged in only one way.
    • Order of the numbers. The loop follows the order of nums, so [3, 1, 2] starts with [3, 1, 2] and ends with [2, 1, 3]. Sorting nums first would change the answer.
    • Forgetting to unmark. If used[i] is never set back to false, the number stays blocked for every later branch, and most permutations never get built.
    • Forgetting to remove from current. The next candidate would be appended after the previous one, so the permutations that follow would start with the wrong numbers.
    • Saving current without a copy. It can pass small tests and fail on larger ones, since it depends on how much capacity append allocated.
    • Starting the loop at i + 1. That is the pattern for subsets and combinations, where order doesn't matter. Permutations need every unused number at every position, so the loop starts at 0.
    • Forgetting the pointer. If result were passed by value, the permutations appended inside backtrack would be lost when it returns.
    • Size. Eight numbers already give 40,320 permutations. Each extra number multiplies the count by the new length.

    Testing

    • [1, 2, 3] gives [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]].
    • [3, 1, 2] gives [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]].
    • [1] gives [[1]].
    • [1, 2, 3, 4, 5, 6, 7, 8] gives 40,320 permutations.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free