Solving tips
- Archetypal backtracking: choose an unused element for each position, recurse, then undo; record a copy when the path reaches length n.
- Track availability with a boolean used array, or generate in place by swapping nums[first] with each nums[i>=first] and swapping back.
- Every choice must be exactly reversed on the way back up (pop / unmark / reverse swap) or sibling branches get corrupted.
- Time is O(n * n!) with O(n) recursion depth; append path[:] (or nums[:]), never a live reference.
Problem
Given an array nums of distinct integers, return all possible permutations of its elements. A permutation is an arrangement that uses every element exactly once; two permutations differ if the elements appear in a different order.
You may return the permutations in any order, but each of the n! distinct orderings must appear exactly once.
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]]
All 3! = 6 orderings of three distinct numbers.
- Input:
nums = [0, 1] β Output: [[0,1], [1,0]]
The two orderings of a pair.
- Input:
nums = [7] β Output: [[7]]
A single element has exactly one permutation.
Constraints
1 <= nums.length <= 6.
-10 <= nums[i] <= 10.
- All integers in
nums are distinct.
With n <= 6, the output has at most 6! = 720 permutations β small, but the enumeration itself is inherently factorial.
Think about it first
Hint 1
Build a permutation one position at a time. For the first slot you may place any element; for the next slot, any element not yet used; and so on. What do you need to remember to avoid reusing an element?
Hint 2
This is textbook backtracking: keep a partial arrangement and a way to know which elements are still available. Place one, recurse, then remove it (undo) and try the next available element.
Hint 3
Track availability with a boolean `used` array, or swap elements into place in the array itself. When the partial arrangement reaches length `n`, you have a complete permutation β record a copy of it.
TL;DR
Backtrack, filling one position at a time using a used marker (or in-place swaps) β O(n Β· n!) time, O(n) recursion depth.
Approach 1 β Backtracking with a used array
The intuition: grow a partial permutation path. At each level, scan all elements and, for any element not yet used, place it, mark it used, recurse, then unmark and remove it. When path has all n elements, snapshot it into the result.
from typing import List
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
result: List[List[int]] = []
path: List[int] = []
used = [False] * n
def backtrack() -> None:
if len(path) == n:
result.append(path[:])
return
for i in range(n):
if used[i]:
continue
used[i] = True
path.append(nums[i])
backtrack()
path.pop()
used[i] = False
backtrack()
return result
Walkthrough on nums = [1, 2, 3]:
- Place
1 β path [1]. Place 2 β [1,2]. Place 3 β [1,2,3] (length 3, record). Undo to [1,2], no more choices; undo to [1]. Place 3 β [1,3], then 2 β [1,3,2] (record).
- Undo back to
[], place 2 β branch yields [2,1,3] and [2,3,1].
- Undo, place
3 β branch yields [3,1,2] and [3,2,1].
Result: all 6 permutations.
Complexity: O(n Β· n!) time β there are n! leaves and copying each completed permutation costs O(n) β and O(n) auxiliary space for path and used (excluding output).
Approach 2 β In-place swapping (no auxiliary used)
The insight: you can generate permutations by fixing one position at a time directly inside the array. At index first, swap each candidate nums[i] (for i >= first) into position first, recurse on first + 1, then swap back to restore the array. This avoids the separate used array and the growing path β the array itself carries the state.
from typing import List
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
result: List[List[int]] = []
def backtrack(first: int) -> None:
if first == n:
result.append(nums[:])
return
for i in range(first, n):
nums[first], nums[i] = nums[i], nums[first]
backtrack(first + 1)
nums[first], nums[i] = nums[i], nums[first]
backtrack(0)
return result
Walkthrough on nums = [1, 2, 3] at first = 0:
i = 0: no-op swap, recurse with [1,2,3] β fixes 1, produces [1,2,3] and [1,3,2].
i = 1: swap positions 0 and 1 β [2,1,3], recurse β produces [2,1,3] and [2,3,1], then swap back to [1,2,3].
i = 2: swap positions 0 and 2 β [3,2,1], recurse β produces [3,2,1] and [3,1,2], swap back.
Same 6 permutations, generated in a different order (order is unconstrained by the problem).
Complexity: O(n Β· n!) time, O(n) recursion-stack space β and no extra used/path buffers, so slightly lower constant-factor memory than Approach 1.
Common pitfalls
- Storing
path by reference. result.append(path) keeps a live alias that mutates; use path[:] (or nums[:]) to record a snapshot.
- Forgetting to undo. Skipping
path.pop() / used[i] = False, or the reverse swap, corrupts sibling branches and yields duplicates or missing permutations.
- Swapping back with the wrong indices. In the in-place version, the restore swap must mirror the exact same
first/i pair, or the array drifts out of its original state.
- Assuming duplicates are handled. This problem guarantees distinct inputs; the plain version would emit repeats on duplicate values β that variant (Permutations II) needs an extra sort-and-skip guard.
Pattern takeaway
Permutation generation is the archetype of βchoose an item for each position, recurse, undo.β Whether you track availability with a used array or by swapping in place, the invariant is the same: every choice you make must be exactly reversed on the way back up so each branch starts from a clean state.