Exercises — Backtracking problems — N-Queens, Sudoku solver, all permutations - subsets
Prerequisites we lean on: DFS, the call stack, and later counting recursive work.
Level 1 — Recognition
L1.1 — Count the subsets
Recall Solution
Each element is a yes/no coin flip: in the subset or out. Four independent flips ⇒ . The formula is where is the count of elements — the same the parent note derived.
L1.2 — Count the permutations
Recall Solution
Fill 5 slots left to right. First slot: 5 choices. Second: 4 remaining. Then 3, 2, 1. .
L1.3 — Which set catches this attack?
Recall Solution
Compute each key. For : . For : . Equal ⇒ both land in the same diag (↘) bucket.
(Check the others fail: columns ; anti-diagonals vs differ.) So the ==diag set== catches it. See the diagonal picture below.

Level 2 — Application
L2.1 — Trace the subset tree
Recall Solution
The code recurses skip before take, so the first leaf reached is "skip everything".
bt(0)→ skip 1 →bt(1)→ skip 2 →bt(2): record[]- back at
bt(1): take 2 →bt(2): record[2] - back at
bt(0): take 1 →bt(1)→ skip 2 → record[1] - take 2 → record
[1,2]
Order: . Exactly the parent's answer.
L2.2 — First two permutations emitted
Recall Solution
The loop always grabs the lowest available index first, so it dives 1 → 2 → 3.
- First leaf: pick
1, pick2, pick3→ record . - Pop back to after "1,2"; no index left ⇒ pop
2. Now pick3, then only2remains → record .
First two: .
L2.3 — Sudoku box index
Recall Solution
(box-row, since rows 6–8 form the bottom band). (box-col, columns 0–2 are the left band). Flatten the grid of boxes: — the bottom-left box.
Level 3 — Analysis
L3.1 — Why the copy path[:]?
Recall Solution
path is one single list object mutated in place (append/pop). res.append(path) stores four references to that same object. When the recursion fully unwinds, every push has a matching pop, so path ends empty.
Result: res = [ [], [], [], [] ] — four aliases of the now-empty list.
. The fix path[:] snapshots the current contents into a fresh list.
L3.2 — Counting valid checks in N-Queens (N=4)
Recall Solution
Queen at occupies: cols = {0}, diag = {0-0=0}, anti = {0+0=0}.
Test each column c in row 1 (r=1):
- :
0 in cols→ pruned. - :
r-c = 0,0 in diag→ pruned (↘ diagonal). - : cols? no.
r-c = -1no.r+c = 3no → survives. - : cols? no.
r-c = -2no.r+c = 4no → survives.
Pruned: . Survivors: (2 columns). This is the pruning power — 2 of 4 branches die instantly.
L3.3 — Time cost of generating all permutations
Recall Solution
There are leaves. At each leaf we copy a list of length into res, costing . So total .
Two factors: = number of complete orderings (leaves of the tree); = cost per snapshot. The internal branching work is dominated by the leaf copies. (See Time-Complexity-of-Recursive-Algorithms for how leaf-count times per-leaf-cost gives the total.)
Level 4 — Synthesis
L4.1 — Subsets with duplicates
Recall Solution
Sort first so equal elements are adjacent: [1,2,2]. Use the index-loop skeleton (choose a start, branch on each next element), and add the rule: within the same loop level, skip an element equal to the previous one.
def subsetsWithDup(nums):
nums.sort(); res, path = [], []
def bt(start):
res.append(path[:])
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]:
continue # skip duplicate sibling
path.append(nums[i])
bt(i+1)
path.pop()
bt(0); return resWhy i > start? The first copy of a value at each level is allowed (it starts a new branch); later equal siblings would re-generate an identical subtree, so we skip them.
Distinct subsets: — 6 of them (not 8).
L4.2 — Combination sum
Recall Solution
Backtrack over a running remain = target - sum. To avoid re-ordering the same multiset, pass a start index and never look left of it.
def combSum(cands, target):
res, path = [], []
def bt(start, remain):
if remain == 0: res.append(path[:]); return
if remain < 0: return # prune overshoot
for i in range(start, len(cands)):
path.append(cands[i])
bt(i, remain - cands[i]) # i (not i+1): reuse allowed
path.pop()
bt(0, target); return resWhy bt(i, ...) not bt(i+1, ...)? Passing i again permits reusing the same candidate; passing start=i still blocks smaller-index numbers, killing reordered duplicates.
Combinations summing to 8: .
Check: ; . (No third: ? yes! — wait, recount below.)
Careful recount of all multisets of summing to 8:
- all 2s: ✓ →
[2,2,2,2] - ✓ →
[2,3,3] - ✓ →
[3,5]
So the true answer is — 3 combinations. (The remain < 0 prune stops paths like that overshoot.)
Level 5 — Mastery
L5.1 — Count N-Queens solutions
Recall Solution
: only two valid full placements exist (they are mirror images). Columns per row: and . Count . : the classic count is distinct solutions. (For reference the sequence of solution counts for is — note are impossible, a degenerate case the solver handles by simply recording nothing.)
L5.2 — Prune Sudoku smarter (MRV heuristic)
Recall Solution
Use Minimum Remaining Values (MRV): among all empty cells, always fill the one with the fewest legal candidate digits first. Why it helps: a cell with only 1 or 2 options has a low branching factor, so recursing there splits the tree into few subtrees. Filling a low-freedom cell early fails fast on dead branches instead of after deep detours. This is a form of Branch-and-Bound / CSP variable ordering — it never removes a valid solution (we still try every candidate of the chosen cell), it only reorders which cell we commit to, so correctness is preserved while the average tree shrinks dramatically. A cell with 0 candidates is an immediate dead end — MRV detects it soonest, the ultimate early prune.
L5.3 — When is backtracking the wrong tool?
Recall Solution
Overlapping subproblems — e.g. counting paths in a grid, or Fibonacci-like recurrences. Backtracking re-explores the same partial state through different choice orders, redoing identical work exponentially. The winner is Dynamic-Programming: it memoizes each distinct subproblem once, turning exponential re-exploration into polynomial table lookups. Backtracking shines when subproblems are distinct (each partial board is unique); DP shines when they repeat.
Recall One-line self-test
Why must every choose have a matching un-choose? ::: Because the state (path, board, used) is shared and mutated; without restoring it, sibling branches inherit stale choices and emit garbage.