Sale: Get 60% Off on all Pro Plans. Buy Now

Permutations

Medium Backtracking

Problem

Given an array of distinct integers nums, return all possible permutations of nums in any order.

  • 1 ≤ nums.length ≤ 6
  • −10 ≤ nums[i] ≤ 10
  • All integers in nums are distinct.

Example

Input: nums = [1, 2, 3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

The brute-force approach would attempt to generate all permutations by trying every possible ordering, but without pruning or systematic exploration, this can be inefficient and complex to implement. The backtracking approach incrementally builds permutations by choosing elements not yet used, exploring deeper only when the current partial permutation is valid. For example, starting with an empty list, the algorithm picks '1', then recursively picks '2', then '3', forming [1,2,3]. It then backtracks to replace '3' with '3' swapped with '2', forming [1,3,2], and so on, systematically exploring all valid permutations without repetition.

Approach

Straightforward Solution

A naive approach might try to generate all permutations by swapping elements in place or by generating all sequences and filtering duplicates, which is inefficient and error-prone. This approach can lead to redundant work or complex bookkeeping.

Core Observation

Generating permutations requires exploring all possible orderings of the input elements, which is factorial in size. The fundamental truth is that each position in the permutation can be filled by any unused element, and the problem breaks down into choosing an element and recursively permuting the remainder.

Path to Optimal

Preview

The key insight is to use backtracking with a partial solution that grows by adding unused elements. At each recursive call, the algorithm tries all elements not yet in the current permutation, appends one, and recurses…

Full step-by-step walkthrough on Pro

Optimal Approach

Preview

Use a recursive backtracking function that maintains the current permutation path. For each call, iterate over all elements in nums, skip those already in the path, append a new element, recurse, then remove it to backtrack…

Full step-by-step walkthrough on Pro

Want the full reasoning chain?

Unlock the complete walkthrough, line-by-line analysis, and recall drill.

Unlock Pro

Time

O(n * n!)

There are n! permutations, and each permutation takes O(n) time to construct and copy into the results, leading to O(n * n!) total time.

Space

O(n)

The recursion stack and current path use O(n) space, where n is the length of nums. The output list uses O(n * n!) space but is not counted as auxiliary space.

Pattern Spotlight

Backtracking (State Restoration)

For problems requiring all permutations or combinations, use a 'choose, explore, un-choose' pattern where you add an element to the current path, recurse to explore deeper, then remove the element to restore state for the next choice.

Solution

Python
1class Solution:
2 def permute(self, nums: List[int]) -> List[List[int]]:
3 def backtrack(curr):
4 if len(curr) == len(nums):
5 ans.append(curr[:])
6 return
7
8 for num in nums:
9 if num not in curr:
10 curr.append(num)
11 backtrack(curr)
12 curr.pop()
13
14 ans = []
15 backtrack([])
16 return ans

Step-by-Step Solution

1

Accumulate Complete Permutations Upon Reaching Full Length

4if len(curr) == len(nums):
5 ans.append(curr[:])
6 return

Objective

To detect when a complete permutation has been formed and add a copy to the results list.

Key Insight

The base case of the recursion occurs when the current path length equals the input length, indicating a full permutation. Appending a copy of the current path ensures that subsequent modifications to the path do not affect the stored result. This step is critical to correctly capture each unique permutation.

Interview Quick-Check

Core Logic

Recognize the base case by comparing current path length to input length and append a copy to results to avoid mutation issues.

State & Boundaries

The base case prevents further recursion and signals a valid solution has been found.

2

Explore Candidates by Adding Unused Elements and Backtracking

To iterate over all input elements, add unused ones to the current path, recurse, and then remove them to restore state.

3

Initialize Result Container and Launch Backtracking

To prepare the results list and start the recursive backtracking with an empty path.

4

Return the Complete List of Permutations

To return the accumulated list of all generated permutations after backtracking completes.

3 more steps with full analysis available on Pro.

Line Analysis

This solution has 3 Critical lines interviewers watch for.

Line 12 Critical
curr.pop()

Remove the last added number to restore the path for the next iteration.

This 'un-choose' step is critical for backtracking, restoring state so other permutations can be explored without interference.

Line 4 Critical
if len(curr) == len(nums):

Check if the current permutation has reached the length of the input array.

This base case identifies when a complete permutation has been formed, signaling that the current path is a valid solution to be recorded.

Line 5 Critical
ans.append(curr[:])

Append a copy of the current permutation to the results list.

Appending a copy prevents future modifications to the current path from altering stored permutations, preserving correctness.

Full line-by-line criticality + rationale for all 11 lines available on Pro.

Test Your Understanding

Why is it necessary to remove the last element from the current path after each recursive call?

See the answer with Pro.

Related Problems

Backtracking pattern

Don't just read it. Drill it.

Reconstruct Permutations from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Permutations drill

or drill a free problem