Return all possible permutations of a given array of unique integers. They can be returned in any order.
Input: nums = [4, 5, 6]
Output: [[4, 5, 6], [4, 6, 5], [5, 4, 6], [5, 6, 4], [6, 4, 5], [6, 5, 4]]
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
You have 3 named chairs and 3 guests. For the first chair you may seat any of the 3 guests; for the second chair, any of the 2 who are still standing; the last chair takes whoever remains. Every distinct way of seating everyone is one permutation. Backtracking seats a guest, recurses to fill the remaining chairs, then stands that guest back up to try seating someone else in that chair.
Our task in this problem is quite straightforward: find all permutations of a given array. The key word here is "all". To achieve this, we need an algorithm that generates each possible permutation one at a time. The technique that naturally fits this requirement is backtracking. As with any backtracking solution, it's useful to first visualize the state space tree.
State space tree
Let's figure out how to build just one permutation. Consider the array [4, 5, 6]. We can start by picking one number from this array for the first position of this permutation. For the second position, let's pick a different number. We can keep adding numbers like this until all the numbers from the array are used. To avoid reusing numbers, let's also keep track of the used numbers using a hash set.
Now that we've found one permutation, let's backtrack to find others. Start by removing the most recently added number, 6, bringing us back to [4, 5]:
Are there any other numbers we can append to [4, 5]? Well, 6 is the only option at this point, which we already explored. So, let's backtrack again by removing 5, bringing us back to [4]:
Are there any numbers other than 5 we can add to [4] at this point? Yes, we can use 6, so let's add it and continue searching:
The only number we can use at this point is 5, so let’s add it to [4, 6], giving us another permutation:
Following this backtracking process until we’ve explored all branches allows us to generate all permutations:
Every time we reach a permutation (i.e., when the permutation we’re building reaches a size of n, where n denotes the length of the input array), add it to our output.
Traversing the state space tree
Generating all permutations can be achieved by traversing the state space tree.
Each node in this tree, except leaf nodes, represents a permutation candidate: a partially completed permutation that we’re building. The root node represents an empty permutation, and an element is added to each permutation candidate as we progress deeper into the tree. The leaf nodes represent completed permutations.
Starting from the root node, we can traverse this tree using backtracking:
Pick an unused number and add it to the current permutation candidate. Mark this number as used by adding it to the used hash set.
Make a recursive call with this updated permutation candidate to explore its branches.
Backtrack: remove the last number we added to the current candidate array, and the used hash set.
Whenever a permutation candidate reaches the length of n, add it to our output.
from typing import List, Set
def find_all_permutations(nums: List[int]) -> List[List[int]]:
res = []
backtrack(nums, [], set(), res)
return res
def backtrack(nums: List[int], candidate: List[int], used: Set[int], res: List[List[int]]) -> None:
# If the current candidate is a complete permutation, add it to the result.
if len(candidate) == len(nums):
res.append(candidate[:])
return
for num in nums:
if num not in used:
# Add 'num' to the current permutation and mark it as used.
candidate.append(num)
used.add(num)
# Recursively explore all branches using the updated permutation
# candidate.
backtrack(nums, candidate, used, res)
# Backtrack by reversing the changes made.
candidate.pop()
used.remove(num)#include <vector>
using namespace std;
void permuteFrom(int start, vector<int>& nums, vector<vector<int>>& res) {
if (start == (int)nums.size()) { // nums is a full permutation
res.push_back(nums); // store a COPY
return;
}
for (int i = start; i < (int)nums.size(); i++) {
swap(nums[start], nums[i]); // swap choice into place
permuteFrom(start + 1, nums, res);
swap(nums[start], nums[i]); // swap back (undo)
}
}
vector<vector<int>> findAllPermutations(vector<int>& nums) {
vector<vector<int>> res;
permuteFrom(0, nums, res);
return res;
}// These methods live inside a class, e.g. class Solution { ... }
List<List<Integer>> findAllPermutations(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
permuteFrom(0, nums, res);
return res;
}
void permuteFrom(int start, int[] nums, List<List<Integer>> res) {
if (start == nums.length) { // nums is a full permutation
List<Integer> copy = new ArrayList<>();
for (int v : nums) copy.add(v); // store a COPY
res.add(copy);
return;
}
for (int i = start; i < nums.length; i++) {
swap(nums, start, i); // swap choice into place
permuteFrom(start + 1, nums, res);
swap(nums, start, i); // swap back (undo)
}
}
void swap(int[] nums, int a, int b) {
int t = nums[a];
nums[a] = nums[b];
nums[b] = t;
}Time complexity: The time complexity of find_all_permutations is . Here’s why:
This results in a total time complexity of .
Space complexity: The space complexity is because the maximum depth of the recursion tree is . The algorithm also maintains the candidate and used data structures, both of which also contribute space. Note, the res array does not contribute to space complexity.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
The highlighted path is the first branch the recursion follows: pick 1, then 2, then 3 → the first finished permutation [1, 2, 3].
| step | candidate | used indexes | action |
|---|---|---|---|
| 1 | [1] | {0} | choose 1 |
| 2 | [1, 2] | {0,1} | choose 2 |
| 3 | [1, 2, 3] | {0,1,2} | full → record [1,2,3] |
| 4 | [1, 3] | {0,2} | undo 2, choose 3 |
| 5 | [1, 3, 2] | {0,1,2} | record [1,3,2] |
| 6 | [2, ...] | {1} | undo to root, choose 2 → record [2,1,3], [2,3,1] |
| 7 | [3, ...] | {2} | choose 3 → record [3,1,2], [3,2,1] |
Six leaves → 3! = 6 permutations. ✓
With an empty array, len(candidate) == len(nums) is 0 == 0 on the very first call, so we record the empty permutation and stop.
| call | candidate | len == n? | action |
|---|---|---|---|
| 1 | [] | 0 == 0 → yes | record [] → return |
Result [[]] — a list containing one empty permutation, not an empty list. Likewise [7] gives [[7]]. ✗
Record a copy with candidate[:] (or nums[:] in the swap version). Python tuples swap cleanly: nums[start], nums[i] = nums[i], nums[start].
res.push_back(candidate) copies the vector for you. The swap version uses std::swap and reuses nums as the buffer.
Copy with new ArrayList<>(candidate). In the swap version we build the copy element by element because nums is an int[].