LeetCode #46 Medium

Permutations

Permutations is LeetCode 46 (Medium). You are given an array nums of distinct integers. Return every permutation of it, that is, every ordering of its elements.

  • Order matters: the same numbers in a different order are a different permutation.
  • Each permutation uses every element exactly once, none repeated and none left out.
  • The permutations may be returned in any order.
Constraints
  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • All the integers of nums are unique.
backtrackingrecursionarray
Open on LeetCode ↗
02

Intuition

Build each ordering one slot at a time. The first slot can take any of the n values, the second any value except the one already placed, and so on. Every different sequence of choices gives a different ordering, so trying all of them lists each permutation exactly once: n × (n-1) × … × 1 = n! of them.

Backtracking walks those choices depth first:

  • Try each open value in the current slot, recurse to fill the next slot, then take the value back out.

The take-back is the whole trick. Every branch shares the same path and used state, so a branch must leave them exactly as it found them, or its next sibling starts from a corrupted state.

How to spot this pattern

Every element must appear and order matters: that is a permutation, and each level of the recursion scans the whole array, skipping what is taken. If order did not matter you would be choosing a subset or combination instead, where a start index moves forward and never looks back.

03

Approach

Try it first

Before reading on: for [1, 2, 3], after [1, 2, 3] is recorded, which values do you take back out, and what is the next permutation the search produces? Aim for O(n · n!) time and O(n) extra space.

1

Grow a path, one slot per level

The recursion depth is the slot being filled, and path holds the values placed so far. When its length reaches n, every slot is full, so path is one finished permutation.

2

Skip values that are already placed

Keep used[i] for every index and, at each level, loop over all indices, skipping the used ones. Scanning from 0 every time is what lets a later slot hold an earlier element, which is the difference from combinations.

3

Choose, recurse, un-choose

Set used[i] = True and append nums[i], recurse, then pop it and set used[i] = False. Undoing in reverse order puts both pieces of state back exactly as they were before the next value is tried.

4

Record a copy at a full path

Append path[:] (Python), new ArrayList<>(path) (Java) or path by value (C++). Backtracking keeps editing the one shared list, so only a copy keeps the finished permutation intact.

5

The swap method, without a used array

Another way keeps the array itself as the path: for slot k, swap each nums[j] with j ≥ k into position k, recurse on k + 1, then swap back. The open values are always the tail nums[k:], so no used array is needed; it is the same O(n · n!), but harder to follow.

04

Permutations solution in Python | C++ | Java

▶1class Solution:
▶2 def permute(self, nums):
▶3 res = []
▶4 path = []
▶5 used = [False] * len(nums)
▶6 
▶7 def backtrack():
▶8 if len(path) == len(nums):
▶9 res.append(path[:])
▶10 return
▶11 for i in range(len(nums)):
▶12 if used[i]:
▶13 continue
▶14 used[i] = True
▶15 path.append(nums[i])
▶16 backtrack()
▶17 path.pop()
▶18 used[i] = False
▶19 
▶20 backtrack()
▶21 return res
value123slot 1slot 2slot 3usedFFFpathnothing placed yetfound0 / 6
path[]
used[F, F, F]
to find63! orderings
Fill slot 1 first. Each row is a slot and each column a value. A permutation puts exactly one value in every slot and uses every value once, so it is one token per row and per column. Slot 1 has all 3 values open, which is where the 3! comes from: 3 choices here, one fewer in each slot below.
value123slot 1slot 2slot 31usedTFFpath1slot 1 ← 1found0 / 6
path[1]
used[T, F, F]
found0 / 6
Slot 1 takes 1. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 312usedTTFpath12slot 2 ← 2found0 / 6
path[1, 2]
used[T, T, F]
found0 / 6
Slot 2 takes 2. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again. 1 is skipped here: already placed above.
value123slot 1slot 2slot 3123usedTTTpath123full → record [1, 2, 3]found1 / 6123
path[1, 2, 3]
used[T, T, T]
found1 / 6
Every slot is full: record [1, 2, 3]. Append a copy, because the search is about to take values back out of this same path. 5 to go.
value123slot 1slot 22slot 331usedTFFpath1undo: pop 3, 2found1 / 6123
path[1]
used[T, F, F]reset 3, 2
Back up. The branch below is exhausted, so pop 3 then 2 off the path and set their flags back to F (dashed circles show where they sat). The shared state is now exactly as it was when slot 2 was first reached, so it can try its next open value cleanly.
value123slot 1slot 2slot 313usedTFTpath13slot 2 ← 3found1 / 6123
path[1, 3]
used[T, F, T]
found1 / 6
Slot 2 takes 3. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again. 1 is skipped here: already placed above.
value123slot 1slot 2slot 3132usedTTTpath132full → record [1, 3, 2]found2 / 6123132
path[1, 3, 2]
used[T, T, T]
found2 / 6
Every slot is full: record [1, 3, 2]. Append a copy, because the search is about to take values back out of this same path. 4 to go.
value123slot 11slot 23slot 32usedFFFpathundo: pop 2, 3, 1found2 / 6123132
path[]
used[F, F, F]reset 2, 3, 1
Back up. The branch below is exhausted, so pop 2 then 3 then 1 off the path and set their flags back to F (dashed circles show where they sat). The shared state is now exactly as it was when slot 1 was first reached, so it can try its next open value cleanly.
value123slot 1slot 2slot 32usedFTFpath2slot 1 ← 2found2 / 6123132
path[2]
used[F, T, F]
found2 / 6
Slot 1 takes 2. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 321usedTTFpath21slot 2 ← 1found2 / 6123132
path[2, 1]
used[T, T, F]
found2 / 6
Slot 2 takes 1. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 3213usedTTTpath213full → record [2, 1, 3]found3 / 6123132213
path[2, 1, 3]
used[T, T, T]
found3 / 6
Every slot is full: record [2, 1, 3]. Append a copy, because the search is about to take values back out of this same path. 3 to go.
value123slot 1slot 21slot 332usedFTFpath2undo: pop 3, 1found3 / 6123132213
path[2]
used[F, T, F]reset 3, 1
Back up. The branch below is exhausted, so pop 3 then 1 off the path and set their flags back to F (dashed circles show where they sat). The shared state is now exactly as it was when slot 2 was first reached, so it can try its next open value cleanly.
value123slot 1slot 2slot 323usedFTTpath23slot 2 ← 3found3 / 6123132213
path[2, 3]
used[F, T, T]
found3 / 6
Slot 2 takes 3. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again. 2 is skipped here: already placed above.
value123slot 1slot 2slot 3231usedTTTpath231full → record [2, 3, 1]found4 / 6123132213231
path[2, 3, 1]
used[T, T, T]
found4 / 6
Every slot is full: record [2, 3, 1]. Append a copy, because the search is about to take values back out of this same path. 2 to go.
value123slot 12slot 23slot 31usedFFFpathundo: pop 1, 3, 2found4 / 6123132213231
path[]
used[F, F, F]reset 1, 3, 2
Back up. The branch below is exhausted, so pop 1 then 3 then 2 off the path and set their flags back to F (dashed circles show where they sat). The shared state is now exactly as it was when slot 1 was first reached, so it can try its next open value cleanly.
value123slot 1slot 2slot 33usedFFTpath3slot 1 ← 3found4 / 6123132213231
path[3]
used[F, F, T]
found4 / 6
Slot 1 takes 3. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 331usedTFTpath31slot 2 ← 1found4 / 6123132213231
path[3, 1]
used[T, F, T]
found4 / 6
Slot 2 takes 1. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 3312usedTTTpath312full → record [3, 1, 2]found5 / 6123132213231312
path[3, 1, 2]
used[T, T, T]
found5 / 6
Every slot is full: record [3, 1, 2]. Append a copy, because the search is about to take values back out of this same path. 1 to go.
value123slot 1slot 21slot 323usedFFTpath3undo: pop 2, 1found5 / 6123132213231312
path[3]
used[F, F, T]reset 2, 1
Back up. The branch below is exhausted, so pop 2 then 1 off the path and set their flags back to F (dashed circles show where they sat). The shared state is now exactly as it was when slot 2 was first reached, so it can try its next open value cleanly.
value123slot 1slot 2slot 332usedFTTpath32slot 2 ← 2found5 / 6123132213231312
path[3, 2]
used[F, T, T]
found5 / 6
Slot 2 takes 2. Mark it used; its column is now closed for every slot below, so the next level cannot pick it again.
value123slot 1slot 2slot 3321usedTTTpath321full → record [3, 2, 1]found6 / 6123132213231312321
path[3, 2, 1]
used[T, T, T]
found6 / 6
Every slot is full: record [3, 2, 1]. Append a copy, because the search is about to take values back out of this same path. That is the last one.
value123slot 13slot 22slot 31usedFFFpathevery choice undone → return 6found6 / 6123132213231312321
answer6 permutations
path[]every choice undone
All 6 found. Every branch undid what it did, so the path is empty and every flag is F again, exactly as at the start. That clean state is what let each sibling branch begin fresh. The work is O(n · n!): 6 results, each copied in O(n).
05

Common pitfalls

Carrying a start index over from combinations

✗ Wrong
def backtrack(start):
    for i in range(start, len(nums)):
        ...
        backtrack(i + 1)
✓ Right
def backtrack():
    for i in range(len(nums)):
        if used[i]:
            continue
        ...

A start index only moves forward, so a later slot can never hold an earlier element. The search then produces subsets in one fixed order, not orderings. Permutations must be able to reach back, which is why taken values are tracked by a flag, not by position.

Appending the live path

✗ Wrong
if len(path) == len(nums):
    res.append(path)
✓ Right
if len(path) == len(nums):
    res.append(path[:])

res ends up holding the same list object n! times, and backtracking pops it back to empty, so the result is a list of empty lists. Java has the same bug with res.add(path); C++ copies on push_back, so it is safe there.

06

Complexity

Time
O(n·n!)
Space
O(n)
There are n! permutations and each costs O(n) to copy into the result, which is also the size of the output, so no algorithm can do better. Extra space is O(n) for path, used and the recursion depth, not counting the output.
07

Permutations vs its look-alikes

These four are easy to mix up. The loop start and the dedupe rule are what change.

ProblemOrder matters?Loop at each levelExtra rule
Permutations (46)yesall indices, skip usednone: values are distinct
Permutations II (47)yesall indices, skip usedsort, and skip nums[i] equal to nums[i-1] when nums[i-1] is not in use
Combinations (77)nofrom start forwardstop at k elements
Next Permutation (31)yesno recursionone in-place step to the next ordering in dictionary order
08

Permutations FAQ

How do you generate all permutations of an array?
  • State: a path of values placed so far and a used flag per index.
  • Leaf: when path has n values, record a copy.
  • Branch: for every unused index, mark it, append its value, recurse, then pop it and unmark it.
  • Complexity: O(n · n!) time, O(n) extra space.
  • Example: [1, 2, 3] gives [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1].
What is the formula for permutations?

n distinct items can be ordered in n! = n × (n-1) × … × 1 ways: n choices for the first slot, n - 1 for the second, and so on. Ordering only r of them gives n! / (n-r)!. That is exactly the shape of the search: n branches at the top, one fewer at each level down.

How do you get the permutations of a list in Python?

itertools.permutations(nums) yields every ordering as a tuple, so [list(p) for p in permutations(nums)] answers the problem in one line. In an interview you are usually asked to write the permutation algorithm in Python yourself, which is the backtracking above; itertools is fine to mention as the library answer.

Why is Permutations on LeetCode O(n · n!) time?

The search reaches n! full paths, and copying each one into the result is O(n). The partial paths above them add up to less than that, since there are n!/1! + n!/2! + … of them, which is under e · n!. The output alone has n · n! numbers, so this is optimal.