Permutations

Fix each position by choosing an unused element, then recurse.

The Search Tree, Step by Step

Start with an empty path: every number is free.
StopSearch treeExport
nums
the first call, running[]
path[]
  • this call
  • on the path
  • answer, in order
  • pruned
  • returned
  • not reached
nums
  1. 1
  2. 2
  3. 3
path
empty
result[]
  • CH1 path

Problem

Given an array of distinct integers, return every possible ordering of those integers. Each number must appear exactly once in each resulting permutation, and the order of the returned permutations does not matter.

Examples

Input: nums = [1, 2, 3]

Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

There are 3 choices for the first position, 2 for the second, and 1 for the last, producing 3! = 6 permutations.

Input: nums = [0, 1]

Output: [[0,1],[1,0]]

Either number may occupy the first position; the other number must occupy the second.

The Goal

Return every ordering of a list of distinct numbers. Each ordering uses every number exactly once.

For [1, 2, 3] there are 3 choices for the first place, 2 for the second and 1 for the last: 3! = 6 orderings.

Every number, once each

A permutation fills every place, and no number appears in it twice. The search fills one place at a time and records only full paths.

n numbers give n! permutations.

Key Idea: Choose, Recurse, Undo

Build the permutation one place at a time, with used[i] true for each number already in the path.

At each call, try every unused number: mark it used, append it, recurse, then pop it and mark it free again. Undoing the choice puts everything back as it was, so the next number starts clean.

Pop it and mark it free, so a sibling branch can use it.

Back at [1], 3 is tried next.

The Algorithm

python
def permute(nums):
    result = []
    path = []
    used = [False] * len(nums)

    def backtrack():
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            backtrack()
            path.pop()
            used[i] = False

    backtrack()
    return result

used makes the check constant time. Testing nums[i] in path works too, but scans the path every time.

ChoiceEffect
A used arrayConstant-time check
nums[i] in pathScans the path each time
Swapping in placeNo used array, but answers come out in a different order

Walkthrough

Run it on [1, 2, 3].

The first call tries 1. Below it, 1 is used, so 2 and then 3 take the second place, and each branch ends on the one number left: [1, 2, 3], then [1, 3, 2].

Undoing 1 frees it, and the first call tries 2, then 3, the same way. The tree has 16 calls, and its 6 leaves are the answers.

Checkpoint · predict the next step

Step 17 of 53 on the stage: Back from 3's branch: pop it and mark it free again. path = [1].

path = [1] · result = [[1, 2, 3], [1, 3, 2]]

What does step 18 show?
Step through it on the stage

Pitfalls

  • Undo both changes: pop the path and set used[i] back to False. Forget one and later branches go missing.
  • Record a copy, path[:], not path, and only when the path is full.
  • With a repeated number, [1, 1, 2] gives each ordering twice. Permutations II skips a repeat at the same depth.
  • The output grows as n!: four numbers already make 24.

A version forgets used[i] = False after the recursive call. What happens on [1, 2, 3]?

Complexity

There are n! permutations, and copying each into the result takes O(n), so time is O(n · n!). The tree's inner calls add less than that.

Apart from the output, space is the path, used and the call stack, all O(n).

Factorial output, linear extra space

Time
O(n · n!)
Space
O(n)
Where the time goes
  • LeavesOne per permutation.O(n!)
  • Copy each permutationn numbers each.O(n · n!)
  • Path, used and call stackNot counting the output.O(n)
How it grows
input size n →opsO(n²)O(2ⁿ)O(n!)O(n log n)O(n)O(log n)O(1)

Factorial, because the answer itself has n! entries.

In short

Backtrack building the permutation position by position, marking elements used as you place them and unmarking on the way back. When the current arrangement uses all elements, record it. A used[] array (or swapping in place) prevents reusing an element.

TimeO(n · n!)SpaceO(n)

Common mistake

Forgetting to unmark an element after recursing, which corrupts sibling branches.