Permutations
Interview Prep
The problem
Given an array of distinct integers, return all n!
permutations. Order of permutations in the output doesn't matter.
Pattern: backtracking with a "used" set
Subsets and Permutations are the two faces of the backtracking template. In Subsets you advance the input index after each pick; in Permutations you don't advance — every unused element is a candidate at every position. So we need a "used" flag (or, in the swap variant, partition the array into "fixed prefix" and "still available suffix").
Emit only at leaves (when the path has length n),
unlike Subsets which emits at every node. That's why Permutations
produces n! outputs, while Subsets produces
2^n.
Solution: used-set backtracking
def permute(nums: list[int]) -> list[list[int]]:
out, path, used = [], [], [False] * len(nums)
def back():
if len(path) == len(nums):
out.append(path.copy())
return
for i in range(len(nums)):
if used[i]: continue
used[i] = True; path.append(nums[i])
back()
path.pop(); used[i] = False
back()
return out Trace
nums = [1, 2, 3]
back([]):
i=0 (1): back([1]):
i=1 (2): back([1,2]):
i=2 (3): back([1,2,3]) -> emit [1,2,3]
i=2 (3): back([1,3]):
i=1 (2): back([1,3,2]) -> emit [1,3,2]
i=1 (2): back([2]):
i=0 (1): back([2,1]):
i=2 (3): emit [2,1,3]
i=2 (3): back([2,3]):
i=0 (1): emit [2,3,1]
i=2 (3): back([3]):
i=0 (1): back([3,1]):
i=1 (2): emit [3,1,2]
i=1 (2): back([3,2]):
i=0 (1): emit [3,2,1]
result (6 permutations): [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] Alternative: in-place swap
A second canonical implementation: instead of a "used" array,
keep the array partitioned into a[:start] (a fixed
prefix being built) and a[start:] (the still-available
multiset). At each step, swap each candidate into position
start and recurse with start + 1. Restore
the swap on the way back.
def permute_swap(nums):
"""In-place: at each step, fix one position by swapping in each candidate."""
out, a = [], nums[:]
def back(start: int):
if start == len(a):
out.append(a.copy()); return
for i in range(start, len(a)):
a[start], a[i] = a[i], a[start]
back(start + 1)
a[start], a[i] = a[i], a[start]
back(0)
return out Variant: Permutations II (duplicates)
If duplicates are allowed, naive backtracking emits the same permutation many times. Fix: sort the input, then within sibling branches at the same recursion level skip a value if its immediately-previous equal copy is currently unused (meaning a sibling branch will explore that copy in that slot — taking this one would produce a duplicate).
def permute_unique(nums: list[int]) -> list[list[int]]:
"""Permutations II — handle duplicates by sorting + skipping repeated siblings."""
nums.sort()
out, path, used = [], [], [False] * len(nums)
def back():
if len(path) == len(nums):
out.append(path.copy()); return
for i in range(len(nums)):
if used[i]: continue
# skip if this value was already used as the same position's choice
if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
continue
used[i] = True; path.append(nums[i])
back()
path.pop(); used[i] = False
back()
return out Complexity
- Time:
O(n · n!)— there aren!permutations, each of lengthnto write out. - Space:
O(n)recursion + the output.
Variations worth knowing
- Next permutation (in place, lex-order): find the
largest
iwitha[i] < a[i+1], the largestj > iwitha[j] > a[i], swap, reverse the suffix.O(n),O(1)space. - k-th permutation: compute factoradic
digits of
k − 1and index into the remaining values. No backtracking needed. - Combinations: the "order doesn't matter" sibling
of Permutations.
C(n, k)outputs; advance the input index instead of using a "used" set. - Lehmer code / inversion vector: a bijection between permutations and tuples in a mixed-radix system. Used in ranking/unranking permutations efficiently.