PrepStack · Markdown HTML

Permutation backtracking

Backtracking and constraint search · HTML

Formal shape

dfs(pos) chooses any unused index i, marks it, recurses to pos+1, then undoes. After sorting, skip nums[i]==nums[i-1] when the previous equal index is not used on the current path.

Recognition signals

False friends

SignalWhy it is different
Order-insensitive selectionUse start-based combination search; permutations create k! equivalent orders.
Only the number of permutationsA factorial or multiset formula, or DP, avoids generation.
Only the best arrangement is wantedGreedy, DP, or assignment structure may optimize without enumerating every order.

Core invariant

At depth pos, path contains exactly pos distinct indices, used matches path exactly, and equal values at one level expand through only their canonical representative.

State, transition, and processing order

ItemDefinition
StateCurrent position, path, and used booleans; alternatively, an in-place array whose prefix [0,pos) is fixed.
TransitionChoose an unused index, mark and append it, recurse, then remove and unmark in reverse order.
Frontier / orderEach tree level represents one output position, with branching decreasing as elements are consumed.

Python skeleton

def unique_permutations(nums):
    nums = sorted(nums)
    used = [False] * len(nums)
    path, out = [], []
    def dfs():
        if len(path) == len(nums):
            out.append(path.copy())
            return
        for i, value in enumerate(nums):
            if used[i]: continue
            if i and nums[i] == nums[i - 1] and not used[i - 1]:
                continue
            used[i] = True; path.append(value)
            dfs()
            path.pop(); used[i] = False
    dfs()
    return out

Correctness sketch

  1. used makes every root-to-leaf path consume each index exactly once, so every leaf is a legal permutation.
  2. For distinct values, every remaining value is available at every position, making every permutation reachable.
  3. Among equal values, the first unused representative is forced at each level; this removes only swaps of indistinguishable values, not distinct value sequences.

Complexity

TimeSpaceParameters
Θ(n·n!) without duplicates, dominated by output copiesO(n) stack/path/used excluding outputn is item count; duplicate counts c_i reduce outputs to n!/∏c_i!.

Edge cases

Error patterns

PatternWhy it fails / fix
Using start instead of usedstart enforces increasing indices and generates combinations, not permutations.
Reversing the duplicate-skip conditionSkip when the previous equal value is not used, which identifies a same-level duplicate.
Failing to unmark usedLater sibling branches permanently lose a candidate.
Generating all then deduplicating with a setThis explores redundant factorial branches and consumes excess memory; prune at the tree level.

Selection and exclusion rules

RelationTemplateBoundary
specializationConstraint-satisfaction backtrackingVariables are positions and domains are unused elements.
contrastSubset / combination backtrackingOrder is irrelevant and start canonicalizes choices.

Original micro-example

PartDetail
ResultAAB, ABA, and BAA appear exactly once.
SetupFind unique arrangements of badges [A,A,B].
TraceAt the root, the second A is skipped while the first A is unused; deeper down it becomes legal after the first A is selected.

Recall prompts

My notes