Permutations
Fix each position by choosing an unused element, then recurse.
The Search Tree, Step by Step
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]]
Input: nums = [0, 1]
Output: [[0,1],[1,0]]
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
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 resultused makes the check constant time. Testing nums[i] in path works too, but scans the path every time.
| Choice | Effect |
|---|---|
| A used array | Constant-time check |
| nums[i] in path | Scans the path each time |
| Swapping in place | No 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]]
Pitfalls
- Undo both changes: pop the path and set
used[i]back toFalse. Forget one and later branches go missing. - Record a copy,
path[:], notpath, 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
- LeavesOne per permutation.O(n!)
- Copy each permutationn numbers each.O(n · n!)
- Path, used and call stackNot counting the output.O(n)
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.
Common mistake
Forgetting to unmark an element after recursing, which corrupts sibling branches.