Pattern 10 of 20
Backtracking
Build candidates one choice at a time and undo choices that cannot lead to an answer.
When to use it
- You must list every combination, permutation or subset
- Puzzles with constraints (placing queens, filling a board)
- The input is small (often n ≤ 15 or so)
The idea
Explore a tree of decisions depth first: choose, recurse, then undo the choice. Pruning branches that already break a constraint is what keeps an exponential search practical.
Typical cost Exponential in the worst case, e.g. O(2ⁿ) subsets or O(n!) permutations.
How it works
Backtracking is a depth-first walk over a tree of decisions. Each level of recursion makes one choice, explores everything that follows from it, then undoes the choice and tries the next. The path from the root to the current call is the partial solution.
The template is always choose, explore, unchoose. Because the same path list is reused and mutated, you must copy it when you record an answer and restore it exactly after each recursive call.
What makes backtracking fast enough is pruning. If a partial solution already violates a constraint (the sum is over target, a queen is attacked, a prefix is not a word), return immediately. Good pruning cuts away most of the tree without changing the exponential worst case.
Spot it when the problem says 'all', 'every combination', 'every arrangement' or 'any valid placement', and the input is small (n up to about 15 or 20). Subsets, permutations, combination sums, N-Queens, Sudoku and word search are all the same skeleton.
How to attack it
- 1Describe one decision per recursion level (include or skip, which item next, which cell).
- 2Write the base case that records a complete answer (copy the path).
- 3List the choices at the current level and the pruning test for each.
- 4Undo every change after the recursive call returns.
- 5Handle duplicates by sorting and skipping equal neighbors at the same depth.
Template
A skeleton to adapt, not a solution to paste. Write it from memory a few times.
def combinations_summing_to(candidates, target):
candidates.sort() # sorting enables pruning and duplicate skipping
results, path = [], []
def backtrack(start, remaining):
if remaining == 0:
results.append(path[:]) # record a COPY
return
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i - 1]:
continue # skip duplicates at this depth
if candidates[i] > remaining:
break # prune: sorted, so the rest are bigger
path.append(candidates[i]) # choose
backtrack(i + 1, remaining - candidates[i]) # explore
path.pop() # unchoose
backtrack(0, target)
return resultsVariants
- Subsets
- At each index, include or skip; or loop from a start index and record every path. 2^n results.
- Permutations
- Track a used array; every unused item is a choice at each level. n! results.
- Combinations with a target
- Loop from a start index so order does not repeat; pass i to allow reuse or i + 1 to forbid it.
- Constraint placement
- N-Queens and Sudoku keep sets of used columns, diagonals or digits so each check is O(1).
- Grid path search
- Mark the cell as visited before recursing and restore it afterwards, so other paths can use it.
Pitfalls
- Appending the path itself instead of a copy, so every recorded answer ends up empty.
- Forgetting to undo the choice, which leaks state into sibling branches.
- Generating duplicate results from repeated input values because equal items are not skipped.
- Using the wrong start index, which mixes up combinations and permutations.
- No pruning on a wide tree, which turns a passing solution into a timeout.
What it costs
| Operation | Cost |
|---|---|
| All subsets | O(n · 2^n) time |
| All permutations | O(n · n!) time |
| Combinations of size k | O(k · C(n, k)) time |
| Recursion depth | O(n) space beyond the output |
30 Backtracking problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Every Topping CombinationFreeEasy
- 2Every Running OrderFreeEasy
- 3Choose a CommitteeFreeEasy
- 4Signal Patterns Without Double PulsesEasy
- 5Case-Flipped PasswordsEasy
- 6Total of Subset XORsEasy
- 7Plus or Minus to TargetEasy
- 8Letter Tile SequencesEasy
- 9Distinct Ingredient BundlesMedium
- 10Distinct Bead StringsMedium
- 11Stamp Combinations for PostageMedium
- 12One-Shot Budget PicksMedium
- 13Well-Nested Bracket StringsMedium
- 14Keypad Word CandidatesMedium
- 15Single-Digit Sum SetsMedium
- 16Recover Dotted AddressesMedium
- 17Mirror Word SplitsMedium
- 18Trace a Word on the BoardMedium
- 19Divisible Seat AssignmentsMedium
- 20Richest Mine RouteMedium
- 21Equal Weight CratesMedium
- 22Fairest Snack HandoutMedium
- 23Countdown Digit StringMedium
- 24Square Chain OrderingsHard
- 25Operators Between DigitsHard
- 26Peaceful Queen PlacementsHard
- 27Complete the Number GridHard
- 28All Words on the BoardHard
- 29Full Sweep RoutesHard
- 30Fewest Deletions to BalanceHard
Solve them in the browser, in Python or JavaScript.