Permutations Solution
Given an array of distinct integers, return all possible permutations. Each element must appear exactly once in every permutation.
Problem Statement
Given an array nums of distinct integers, return all the possible permutations in any order. For n elements, there are exactly n! permutations.
Swap-based In-place โ O(n ยท n!)
Fix one element at position 0 by swapping it with each element in turn, then recursively permute the rest of the array. After recursion, swap back (backtrack) to restore the original order. The result is collected when the recursion depth equals n.
Every permutation can be constructed by choosing which element appears first, then permuting the rest. The swap-then-recurse approach systematically places each possible element at the front, then restores the array state after exploring that branch.
path list, never touches the input, and clearly mirrors the choose/unchoose pattern used for subsets and combinations, making it easier to adapt to variants with duplicates (LC 47).
| Metric | Value |
|---|---|
| Time | O(n ยท n!) โ n! permutations, O(n) to copy each |
| Space | O(n) call stack (excluding output) |
Backtracking with Used-Array โ O(n ยท n!)
Maintain a boolean used[] array to track which elements are already in the current path. At each position, try every unused element, add it, recurse to the next position, then remove it and mark it unused.
used[] array โ you mark off a guest when they sit down and erase the mark when they stand up.
- State:
path(current permutation being built),used[](which elements are in path). - Base case:
path.size() == nโ add copy of path to result. - Loop: for each index
iinnums: ifused[i]is false, mark used, add to path, recurse, remove from path, mark unused. - No start index needed โ unlike combinations, permutations consider all elements at every level.
- "All possible orderings / arrangements of distinct elements." Permutation problems don't have a start-index constraint (every unused element is fair game at each level), which distinguishes them from combination problems.
- This template extends directly to LC 47 (Permutations II, with duplicates โ add a sort + skip-duplicates guard).
- For small n (โค 6 per constraints), a boolean array indexed by position is O(1) and cache-friendly.
- A HashSet has higher constant factor overhead.
- If the values were large integers, a set would be needed โ but since indices 0..nโ1 are bounded, the array wins.
Visual Walkthrough
Trace for nums = [1, 2, 3]. Each level picks one unused element. The full tree has 6 leaves (3! = 6).
Code โ Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| Iterative (insert into positions) | O(n ยท n!) | O(n ยท n!) | No recursion overhead but harder to extend to duplicate handling |
| Swap-based backtracking | O(n ยท n!) | O(n) | Mutates input; subtle to debug |
| Used-array backtracking โ optimal | O(n ยท n!) | O(n) | Clean, non-mutating, trivially extensible to LC 47 (duplicates) |
- There are n! permutations and for each one we copy the path (O(n)) to the result list.
- Nodes in the backtracking tree: n! leaves + internal nodes (roughly e ยท n! โ 2.7 ยท n!).
- Total work is bounded by O(n ยท n!) despite the internal node overhead.
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| Single element | [42] | Only one permutation: [[42]]. Base case fires immediately. |
| Two elements | [1, 2] | Two permutations: [[1,2],[2,1]]. Smallest non-trivial case. |
| Negative numbers | [-1, 0, 1] | No comparison used โ used[] tracks indices, not values. Works correctly. |
| Max input n=6 | 6 elements | 6! = 720 permutations. Call stack depth = 6. Well within limits. |
| Forgetting to unset used[i] | any | Not backtracking used[i] = false makes all future branches think the element is still used. Zero permutations after the first. |
| Storing reference instead of copy | any | res.add(path) leads to all results being the same (empty) list at the end. |
used[i] = false after the recursive call. The used[] array is shared across all levels of recursion. If you don't unset it, once an element is used on any path, it stays "used" for all future paths. Every permutation after the first will be missing that element. The backtrack and the mark/unmark must always appear as a matched pair.