Reasoning Into CodeArunank Divya Sharan · Python edition← Arunank’s portfolio
Module 16 · Backtracking and Combinatorial Search

154. Generate Permutations

Foundation · Download this solution · All solutions and shared helpers

Problem

Return all permutations of distinct integers in nums. At each position, try still-unused input indices in their original order. Empty input returns [[]]. Leave nums unchanged and return independent lists.

Examples

Example 1

permutations([1,2])
=> [[1, 2], [2, 1]]

Order makes the two leaves different.

Example 2

permutations([])
=> [[]]

There is one arrangement of no values.

Example 3

permutations([8])
=> [[8]]

Only one index can be placed.

Reveal the worked solution and reasoning

Why this mechanism works

Unlike a subset, a permutation distinguishes different orders. There is no increasing-index restriction: any unused index may fill the next position. The state therefore needs a used flag for every index. Each recursive frame owns one placement and must restore both that flag and the path when it returns.

State and boundaries

path contains distinct chosen input positions in order. used[i] is true exactly when index i appears in path.

Protocol

  1. Save path at length n.
  2. Scan all indices.
  3. Skip used indices.
  4. Mark an unused index and append its value.
  5. Recurse, then pop and clear its flag.

Visualization

[1,2] -> place 1 -> place 2 -> [1,2]
undo both choices
      -> place 2 -> place 1 -> [2,1]

Python implementation

def permutations(nums):
    output, path = [], []
    used = [False] * len(nums)
    def visit():
        if len(path) == len(nums):
            output.append(path.copy())
            return
        for i, value in enumerate(nums):
            if used[i]:
                continue
            used[i] = True
            path.append(value)
            visit()
            path.pop()
            used[i] = False
    visit()
    return output

Worked execution

Recorded state changes after statements; L numbers refer to the solution file. Long traces are explicitly condensed.

permutations([1,2])

L2 permutations: output, path = [], []
nums=[1, 2]; output=[]; path=[]

L3 permutations: used = [False] * len(nums)
nums=[1, 2]; output=[]; path=[]; used=[False, False]

L5 visit: if len(path) == len(nums):
nums=[1, 2]; output=[]; path=[]; used=[False, False]

L8 visit: for i, value in enumerate(nums):
nums=[1, 2]; output=[]; path=[]; used=[False, False]; i=0; value=1

L11 visit: used[i] = True
nums=[1, 2]; output=[]; path=[]; used=[True, False]; i=0; value=1

L12 visit: path.append(value)
nums=[1, 2]; output=[]; path=[1]; used=[True, False]; i=0; value=1

L5 visit: if len(path) == len(nums):
nums=[1, 2]; output=[]; path=[1]; used=[True, False]

L8 visit: for i, value in enumerate(nums):
nums=[1, 2]; output=[]; path=[1]; used=[True, False]; i=0; value=1

... 24 intermediate state changes omitted ...

L13 visit: visit()
nums=[1, 2]; output=[[1, 2], [2, 1]]; path=[2]; used=[False, True]; i=1; value=2

L14 visit: path.pop()
nums=[1, 2]; output=[[1, 2], [2, 1]]; path=[]; used=[False, True]; i=1; value=2

L15 visit: used[i] = False
nums=[1, 2]; output=[[1, 2], [2, 1]]; path=[]; used=[False, False]; i=1; value=2

L16 permutations: visit()
nums=[1, 2]; output=[[1, 2], [2, 1]]; path=[]; used=[False, False]

Result: [[1, 2], [2, 1]]. Order makes the two leaves different.

permutations([])

L2 permutations: output, path = [], []
nums=[]; output=[]; path=[]

L3 permutations: used = [False] * len(nums)
nums=[]; output=[]; path=[]; used=[]

L5 visit: if len(path) == len(nums):
nums=[]; output=[]; path=[]; used=[]

L6 visit: output.append(path.copy())
nums=[]; output=[[]]; path=[]; used=[]

L16 permutations: visit()
nums=[]; output=[[]]; path=[]; used=[]

Result: [[]]. There is one arrangement of no values.

Why the solution is correct

The flags prevent using a position twice, so every leaf is a valid permutation. Any permutation specifies a unique sequence of unused-index choices that the search explores. Restoring both structures preserves the state for sibling branches. Each placement reduces the number of unused indices.

Time and space

O(n*n!) time including copying answers and scanning candidate indices; O(n*n!) output space and O(n) auxiliary state.

Common mistakes

A global visited set that is never cleared turns path-local usage into permanent exclusion. Distinct values are required for uniqueness here.

Reasoning card

State: path contains distinct chosen input positions in order. used[i] is true exactly when index i appears in path.

Invariant: Path plus unused values equals the original set.

Decision to rehearse: At each position choose one unused value.

Finish: Recurse, then pop and clear its flag

Transfer exercise

Use in-place swaps to place each prefix position, and identify exactly which swap must be undone.