3.7.17 · D5Algorithm Paradigms

Question bank — Backtracking problems — N-Queens, Sudoku solver, all permutations - subsets

1,751 words8 min readBack to topic

Before we start, plain-word reminders so nothing below uses an unexplained term:


True or false — justify

Backtracking always explores fewer nodes than brute force.
False — in the worst case (no branch can ever be pruned, e.g. permutations where every ordering is valid) it visits the same full tree; pruning only helps when constraints let you kill branches early.
Every recursive algorithm is a backtracking algorithm.
False — backtracking specifically mutates a shared state and then restores it (choose → explore → un-choose); plain recursion may pass fresh copies and never undo anything.
If you never write the "un-choose" line, the code just runs slower but still gives correct answers.
False — it gives wrong answers: leftover choices leak into sibling branches, so later results inherit garbage (e.g. queens still "placed" on the board).
Storing path instead of path[:] only wastes memory.
False — it stores a reference to one list that keeps mutating, so all your saved answers end up identical (usually all empty), which is a correctness bug not a memory one.
The subset tree and the permutation tree have the same shape.
False — subsets branch into a binary "take/skip" tree with leaves; permutations branch into a shrinking tree with leaves, and permutations need a used array while subsets need only an index.
N-Queens and Sudoku use the same "collect all solutions" strategy.
False — N-Queens (as written) collects all solutions and returns nothing, exploring every branch; the Sudoku solver wants one solution and returns a bool to short-circuit the moment a full fill succeeds.
Placing one queen per row is a heuristic that might miss solutions.
False — it loses nothing: any valid solution has exactly one queen per row anyway (two in a row would attack), so we only remove branches that were guaranteed invalid.
Checking validity at the leaf gives the same speed as checking before recursing.
False — leaf-only checking is essentially brute force; you generate every doomed configuration fully before rejecting it, throwing away backtracking's entire advantage.
The used array in permutations could be replaced by scanning path for the element.
True in correctness, but scanning costs per check versus for the array — same answers, worse constant factor.
Tracking only the r-c diagonal set (not r+c) is enough to catch all diagonal attacks.
False — r-c catches only the ↘ diagonals; ↗ attacks share a constant r+c, so without the anti-diagonal set two queens on a ↗ line slip through undetected.

Spot the error

"I return early right after the recursive call in N-Queens to speed it up."
Error — N-Queens must collect every solution, so returning early would abandon sibling columns and miss valid boards; early-return is only correct when you want a single answer (like Sudoku).
"For subsets I append path and it works because the list is short."
Error — length is irrelevant; every appended reference points to the same mutating list, so after recursion finishes all entries read as []; you must append path[:].
"In permutations I set used[i]=True (where i is the current loop index) but skip resetting it since each element is used once."
Error — it's used once per branch, not once total; without used[i]=False on the way up, later top-level branches ("start with 2") can never reuse element 1.
"My Sudoku ok() checks row and column but I compute the box as r*3+c."
Error — that isn't the box; the box index is (r//3)*3 + c//3, and inside it you must iterate cells (b//3)*3 + i//3, (b%3)*3 + i%3 — using r*3+c mixes up cell coordinates with box coordinates.
"To prune Sudoku I check board[r][i]==ch (where ch is the digit I'm trying) only for the current column c."
Error — the row constraint requires scanning all columns i in that row (and all rows in that column); checking one cell validates nothing.
"I mutate board[r][c]='Q' but forget to also add to cols, diag, anti."
Error — the sets are what give conflict checks; if they aren't updated alongside the board, later rows won't see the queen and will place attackers.
"I call bt(i+1) (the recursive backtracking function, advancing the index) for 'take' but forget path.pop() afterward in subsets."
Error — without the pop, the taken element leaks into the sibling "skip" branch's state, corrupting every subsequent subset.

Why questions

Why does each element being independently in-or-out give subsets (with the count of input elements)?
Because the choices multiply: options for the first element for the second over all elements total combinations, one per subset.
Why is r - c constant along a ↘ diagonal?
Moving down-right adds to both r and c, so their difference never changes; that single number therefore labels the whole diagonal.
Why does Sudoku return a bool up the call stack but N-Queens returns nothing?
Sudoku wants one solution and uses True to stop all further searching the instant it's found; N-Queens wants all solutions, so it never short-circuits and just records each one.
Why must we validate before applying a choice, not after?
Validating first lets a dead branch die at its root before we generate its whole subtree — that early death is exactly the pruning that separates backtracking from brute force.
Why is "one queen per row" better than "any of squares"?
It removes all row-conflicts for free and cuts the branching factor from candidate squares down to columns per row, shrinking the tree dramatically.
Why does the maze analogy fit backtracking?
In a maze you walk back only to the last fork and try another path rather than restarting; backtracking likewise undoes just the most recent choice and tries the next sibling, reusing all the correct work above it.
Why does un-choose restore state to before the choice, not to empty?
Because the parent node's partial solution is still valid and shared by all its children; you only undo your increment so the next sibling inherits the same correct prefix.

Edge cases

What does subsets([]) return?
Exactly one subset — the empty set [[]] — because with there are subsets and the base case records the empty path.
What does permute([]) return?
One permutation, the empty ordering [[]], since and len(path) == len(nums) is immediately true.
Does N-Queens have a solution for or ?
No — both are impossible; the tree is fully explored and res comes back empty, which is a correct result, not a bug.
What does the Sudoku solver do on an already-complete valid board?
bt() finds no empty cell, so it immediately hits the "no empty cell → solved" branch and returns True without changing anything.
What does the Sudoku solver do on an unsolvable board?
Every digit fails at some empty cell, each level returns False, the recursion unwinds fully, and the board is left restored to its original .-filled state.
What happens in permutations when nums has duplicates like [1,1]?
The plain used-array version produces duplicate orderings ([1,1] twice) because it only tracks positions, not values; deduping needs an extra "skip equal siblings" rule beyond this template.
If candidates(state) is ever empty at a non-solution node, what happens?
The for loop body never runs, the function returns having recorded nothing, and control unwinds to the parent — a natural dead-end handled with no special code.
Recall One-line self-test

The three magic words of every backtracking routine are choose → explore → un-choose, and the un-choose step is the one that stops sibling branches from inheriting each other's garbage.