Backtracking — try, recurse, un-try
Backtracking
Backtracking is DFS over a decision tree. At each node: choose a candidate, recurse, un-choose. That symmetric third step lets you reuse one mutable buffer.
Template:
def dfs(state):
if is_goal(state): record(state); return
for choice in candidates(state):
apply(choice)
dfs(state)
undo(choice)
Permutations vs subsets
Permutations need a used marker. Branching factor at depth k = n - k. n! leaves.
Subsets are binary at each index: take or skip. 2ⁿ leaves. Generalizes to combinations and combination-sum.
N-Queens flavour
Same skeleton + three pruning sets: occupied columns, occupied row+col diagonals, occupied row-col anti-diagonals. Place row by row.
Common pitfalls
- Appending the live list.
out.append(path)stores a reference. Snapshot:path[:]. - Forgetting to un-choose. One missed
popcorrupts every sibling branch. - Duplicate results. Sort input, skip
nums[i] == nums[i-1]wheni > start. - Recursion depth. Python's 1000-limit. Bump or iterate.
When to reach for it
Combinatorial objects (arrangements, selections, paths) where constraints prune aggressively. If search space is 2ⁿ or n! with no pruning, need memoization or smarter formulation.
Try it
- Add
kparameter tosubsets— only size-k combinations. Prune whenlen(path) + remaining < k. - Write
solve_n_queens(n)returning count of valid boards. Expected for n=8: 92.
3 graded exercises
Write the code, click Check. We’ll tell you exactly what to fix.
- Exercise 1
Write
all_subsets(nums)that returns every subset of the input list (the power set). Include the empty subset. Order of subsets doesn't matter, but each subset should preserve input order. - Exercise 2
Write
all_permutations(nums)that returns every ordering ofnums. Assume input has no duplicates. - Exercise 3
On a phone keypad, 2='abc', 3='def', 4='ghi', 5='jkl', 6='mno', 7='pqrs', 8='tuv', 9='wxyz'. Write
letter_combos(digits)that returns every letter combination the given digit string could spell. Return [] for empty input.