Permutations

Fix each position by choosing an unused element, then recurse.

Approach

Backtrack building the permutation position by position, marking elements used as you place them and unmarking on the way back. When the current arrangement uses all elements, record it. A used[] array (or swapping in place) prevents reusing an element.

Time complexity

O(n · n!)

Space complexity

O(n)

Common mistake

Forgetting to unmark an element after recursing, which corrupts sibling branches.

Interactive walkthrough — step through it below, free and without an account

Problem

Given an array of distinct integers, return every possible ordering of those integers. Each number must appear exactly once in each resulting permutation, and the order of the returned permutations does not matter.

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]]

There are 3 choices for the first position, 2 for the second, and 1 for the last, producing 3! = 6 permutations.

Input: nums = [0, 1]

Output: [[0,1],[1,0]]

Either number may occupy the first position; the other number must occupy the second.

Problem

Problem

Given an array of distinct integers, return every possible ordering of those integers. Each number must appear exactly once in each resulting permutation, and the order of the returned permutations does not matter.

Problem and Goal

Learning goal: Recognize permutations as a search over choices for each position.

Core idea A permutation is complete only when every input element has been placed exactly once. At the first position, any element is possible; after choosing one, only the remaining elements can fill the next position. This creates a branching decision tree whose leaves are the answers.

Definition

A permutation is an arrangement containing every input element once, with order affecting the result.

Algorithm plan

  • Start with an empty path representing the current arrangement.
  • Try each element that is not already used.
  • Place it in the next position and recurse.
  • Record a copy when path.length == nums.length.

Micro-example For [1,2,3], choosing 1 first fixes the prefix [1]. The next position can then choose 2 or 3, creating two sibling branches.

Pitfalls

  • Do not reuse an element already in path.
  • Record a copy of path, not the mutable working array itself.
  • The order of generated answers may differ from the example.

Recap

  • Every level fixes one position.
  • Every leaf is one complete permutation.
  • Distinct inputs make used membership unambiguous.

Problem and Goal

Build one position at a time

At depth `k`, the first `k` positions are fixed. Choose one unused number for position `k`, recurse, then undo that choice.

A complete path of length `n` is one permutation.

Backtracking Intuition

Learning goal: Understand why making and undoing choices explores every valid ordering.

Core idea Backtracking treats the partial permutation as state. A recursive call extends that state by one unused value; when the call returns, the value is removed so the caller can try a different sibling choice. The same array can therefore represent many branches without permanently contaminating later decisions.

Key insight

The recursion does not guess the whole answer at once. It fixes exactly one position, explores all completions, then changes that position.

Algorithm plan

Code
backtrack(path, used):
    if path contains every element:
        save a copy of path
        return

    for each index i:
        if used[i]:
            continue
        used[i] = true
        append nums[i] to path
        backtrack(path, used)
        remove last item from path
        used[i] = false

The used[] array answers whether an element is already present in the current branch. The two undo operations restore the exact state that existed before the recursive call.

Micro-example After building [1,2,3], return to [1,2]: unmark 3, then remove it. Now the algorithm can return to [1] and try 3 in the second position.

Pitfalls

  • Mark before recursing and unmark after recursing.
  • Remove the last path item, not an arbitrary value.
  • Pass or maintain state consistently at every depth.

Recap

  • Choose, recurse, undo is the backtracking pattern.
  • used[] prevents duplicate use within one branch.
  • Undo restores sibling branches to a clean state.

Backtracking Intuition

Remove the value and unmark it so sibling branches start clean.

[1,2,3] -> [1,2]

Main Approach and Correctness

Learning goal: Translate the backtracking idea into a correct implementation and justify its completeness.

Core idea At recursion depth k, the algorithm has already fixed positions 0 through k - 1. It tries every unused input index for position k, so no legal next choice is skipped. Because the choice is undone after its entire subtree is explored, every possible continuation is considered independently.

Algorithm plan

  1. Initialize an empty path, a used array of length n, and an empty answers list.
  2. If path has length n, append a copy to answers.
  3. Otherwise, loop through all indices and skip those marked used.
  4. Mark the chosen index, append its value, and call backtrack().
  5. Pop the value and unmark the index before trying the next candidate.

Why it works

Every root-to-leaf path chooses each input index once, so it forms a valid permutation. Every ordering has a unique sequence of choices, so the search reaches every answer exactly once.

Micro-example For [1,2,3], the root has children [1], [2], and [3]. The [1] subtree contains [1,2,3] and [1,3,2].

Pitfalls

  • Forgetting used[i] = false after recursion corrupts sibling branches.
  • Forgetting to pop from path leaves old values in later permutations.
  • Use a copied path when recording a solution.

Recap

  • Depth equals the next position to fill.
  • The loop supplies all legal choices at that position.
  • Undo is required for correctness, not merely optimization.

Main Approach and Correctness

ApproachStateUndo step
Used array`path` + `used[]`Pop path item and set `used[i] = false`
In-place swappingOne array with fixed prefixSwap the value back after recursion

End-to-End Walkthrough

Learning goal: Trace all six answers for the concrete input [1,2,3].

Core idea The recursion explores the tree in depth-first order. It first commits to 1, fully explores every completion beginning with 1, then backs up and commits to 2, and finally to 3. The used[] state always describes only the current path.

Algorithm plan

  • Begin at the root with path = [].
  • Follow the first available candidate at each level.
  • At depth three, record the completed arrangement.
  • Backtrack one level and replace the most recent choice.
  • Continue until the root has no unused choices.

Micro-example The first branch is [1] -> [1,2] -> [1,2,3], which records the first answer. Undoing 3 enables [1,3,2] without rebuilding the entire search from scratch.

Reading the visualization

A choose event extends path; a record-solution event reaches a leaf; a backtrack event removes the latest choice and restores availability.

Pitfalls

  • A backtrack event must restore both path and used[].
  • Sibling branches share a parent prefix but not a child choice.
  • The final count should be 3! = 6.

Recap

  • Depth-first search records [1,2,3], [1,3,2], then the two 2 branches and two 3 branches.
  • Every leaf has length three.
  • Clean restoration makes the six branches independent.

End-to-End Walkthrough

Animates the complete depth-first backtracking trace for nums = [1, 2, 3].

Backtracking / recursion tree
1 / 17
Fix position 0 with 1; remaining choices are 2 and 3.

Path

1

Choices

2
3
Depth: 1Solutions: 0

Action

choose
A recursive level fixes one position.Every value in path is used exactly once.
1 / 17

Pitfalls and Edge Cases

Learning goal: Diagnose the state-restoration bug and handle small inputs confidently.

Core idea Backtracking relies on a strict contract: a recursive call may change state temporarily, but it must return the state to its caller's version. If an element remains marked after its branch finishes, later siblings incorrectly believe that element is unavailable, so valid permutations disappear.

Common pitfall

Forgetting to unmark an element after recursing corrupts sibling branches. Always pair used[i] = true with used[i] = false after the recursive call.

Algorithm plan

  • Before recursion: mark the index and append the value.
  • After recursion: pop the value and unmark the index.
  • For n = 1, the single element is immediately a complete answer.
  • Because values are distinct, no duplicate-skipping rule is needed.

Micro-example If [1] is left marked after exploring [1,2,3], the root cannot later choose 1 for branches beginning with 2 or 3. The result loses valid answers such as [2,1,3].

Pitfalls

  • Empty path is not a solution unless the problem allowed n = 0; here n >= 1.
  • Do not mutate the saved solution when later backtracking changes path.
  • Swapping in place also requires swapping back after recursion.

Recap

  • Restoration is part of the algorithm's correctness proof.
  • The smallest input has exactly one permutation.
  • Distinctness means each index can be tracked directly.

Pitfalls and Edge Cases

What must happen after backtrack(path, used) returns?

Complexity and Wrap-up

Learning goal: Analyze the cost of permutation backtracking and summarize the reusable pattern.

Core idea There are n! complete permutations. Constructing or copying each answer costs O(n), so the total time is O(n · n!). The recursion depth, path, and used[] each require linear auxiliary space; excluding the returned output, auxiliary space is O(n).

Algorithm plan

  • Count the n! leaves in the decision tree.
  • Account for O(n) work to copy each complete path.
  • Account for O(n) recursion depth and state arrays.
  • Treat the output storage separately because it necessarily contains all answers.

Micro-example For n = 3, there are 3! = 6 leaves. Copying three values at each leaf gives work proportional to 3 · 6 = 18, which matches the O(n · n!) bound.

Pitfalls

  • Do not report only O(n!) when every answer must be copied.
  • Output storage is O(n · n!), even though auxiliary working space is O(n).
  • Backtracking does not avoid the factorial number of answers; it organizes their generation.

Recap

  • Time: O(n · n!), including output construction.
  • Auxiliary space: O(n), excluding returned permutations.
  • The transferable pattern is choose, recurse, and undo.

Complexity and Wrap-up

Summarizes factorial output size and linear auxiliary state.

Complexity analysis
Final complexity: O(n · n!) time and O(n) auxiliary space.
Time
O(n · n!)
Space
O(n)
Where the time goes
  • Explore all permutation leavesThere are n! distinct orderings.O(n!)
  • Copy each completed pathEach recorded answer contains n values.O(n)
  • Auxiliary recursion and used stateDepth, path, and used[] are linear.O(n)
How it grows
input size n →opsO(n²)O(2ⁿ)O(n!)O(n log n)O(n)O(log n)O(1)

The factorial cost is unavoidable because the algorithm must output all n! permutations.

Now try it on your own problem

Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.

Build a lesson from any problem

Related problems