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

  1. 1Describe one decision per recursion level (include or skip, which item next, which cell).
  2. 2Write the base case that records a complete answer (copy the path).
  3. 3List the choices at the current level and the pruning test for each.
  4. 4Undo every change after the recursive call returns.
  5. 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 results

Variants

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

OperationCost
All subsetsO(n · 2^n) time
All permutationsO(n · n!) time
Combinations of size kO(k · C(n, k)) time
Recursion depthO(n) space beyond the output

30 Backtracking problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Every Topping CombinationFreeEasy
  2. 2Every Running OrderFreeEasy
  3. 3Choose a CommitteeFreeEasy
  4. 4Signal Patterns Without Double PulsesEasy
  5. 5Case-Flipped PasswordsEasy
  6. 6Total of Subset XORsEasy
  7. 7Plus or Minus to TargetEasy
  8. 8Letter Tile SequencesEasy
  9. 9Distinct Ingredient BundlesMedium
  10. 10Distinct Bead StringsMedium
  11. 11Stamp Combinations for PostageMedium
  12. 12One-Shot Budget PicksMedium
  13. 13Well-Nested Bracket StringsMedium
  14. 14Keypad Word CandidatesMedium
  15. 15Single-Digit Sum SetsMedium
  16. 16Recover Dotted AddressesMedium
  17. 17Mirror Word SplitsMedium
  18. 18Trace a Word on the BoardMedium
  19. 19Divisible Seat AssignmentsMedium
  20. 20Richest Mine RouteMedium
  21. 21Equal Weight CratesMedium
  22. 22Fairest Snack HandoutMedium
  23. 23Countdown Digit StringMedium
  24. 24Square Chain OrderingsHard
  25. 25Operators Between DigitsHard
  26. 26Peaceful Queen PlacementsHard
  27. 27Complete the Number GridHard
  28. 28All Words on the BoardHard
  29. 29Full Sweep RoutesHard
  30. 30Fewest Deletions to BalanceHard

Solve them in the browser, in Python or JavaScript.