3.7.17Algorithm Paradigms

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

2,031 words9 min readdifficulty · medium

WHY does backtracking exist?

WHAT is the search space? A tree. Each node = a partial solution. Each edge = "make one more choice." Leaves = complete candidates.

HOW do we walk it? Recursive DFS with this universal skeleton:

The "un-choose" line is what makes backtracking different from plain recursion: the state is shared and mutated, so you must restore it before trying a sibling branch.

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

Pattern 1 — All Subsets (the simplest skeleton)

WHY 2n2^n? Each element is independently in or out2×2×=2n2 \times 2 \times \dots = 2^n.

HOW — at index i, branch on "take nums[i]" vs "skip it":

def subsets(nums):
    res, path = [], []
    def bt(i):
        if i == len(nums):
            res.append(path[:])      # copy! path keeps mutating
            return
        bt(i+1)                      # skip nums[i]
        path.append(nums[i])         # choose
        bt(i+1)                      # explore
        path.pop()                   # un-choose
    bt(0)
    return res

Pattern 2 — All Permutations

WHY n!n!? nn choices for first slot, n1n-1 for next, … =n!= n!.

HOW — track which elements are used:

def permute(nums):
    res, path, used = [], [], [False]*len(nums)
    def bt():
        if len(path) == len(nums):
            res.append(path[:]); return
        for i in range(len(nums)):
            if used[i]: continue     # pruning: each element once
            used[i] = True; path.append(nums[i])   # choose
            bt()                                    # explore
            path.pop(); used[i] = False             # un-choose
    bt()
    return res

Pattern 3 — N-Queens

HOW do we check a column/diagonal in O(1)O(1)?

  • Same column → cols[c] seen before.
  • Same ↘ diagonal: cells where row - col is constant. (Derive: moving down-right adds 1 to both r and c, so r-c unchanged.)
  • Same ↗ anti-diagonal: row + col is constant.

So we keep three sets: cols, diag (r-c), anti (r+c).

def solveNQueens(n):
    res = []
    cols, diag, anti = set(), set(), set()
    board = [['.']*n for _ in range(n)]
    def bt(r):
        if r == n:
            res.append([''.join(row) for row in board]); return
        for c in range(n):
            if c in cols or (r-c) in diag or (r+c) in anti:
                continue                         # prune attacked square
            cols.add(c); diag.add(r-c); anti.add(r+c)   # choose
            board[r][c] = 'Q'
            bt(r+1)                                       # explore
            cols.remove(c); diag.remove(r-c); anti.remove(r+c)  # un-choose
            board[r][c] = '.'
    bt(0)
    return res

Pattern 4 — Sudoku Solver

HOW — find next empty cell, try digits 1–9, recurse, undo on failure:

def solveSudoku(board):                # board: list of lists, '.' = empty
    def ok(r, c, ch):
        b = (r//3)*3 + c//3            # box index
        for i in range(9):
            if board[r][i]==ch: return False     # row
            if board[i][c]==ch: return False     # col
            if board[(b//3)*3 + i//3][(b%3)*3 + i%3]==ch: return False  # box
        return True
    def bt():
        for r in range(9):
            for c in range(9):
                if board[r][c]=='.':
                    for ch in '123456789':
                        if ok(r,c,ch):
                            board[r][c]=ch     # choose
                            if bt(): return True   # explore
                            board[r][c]='.'    # un-choose
                    return False               # no digit works → dead end
        return True                            # no empty cell → solved
    bt()

Steel-manned mistakes


Recall Feynman: explain to a 12-year-old

Imagine solving a maze by hand. You walk forward making choices. If you hit a wall, you walk back to the last fork and try a different path — you don't restart the whole maze. Backtracking is that: try a step, if it's hopeless erase it and try another. For queens, you place chess queens one row at a time; if a new queen could be captured, you pick it up and slide it over. That "pick it up and put it back in the box" is the secret move.


Flashcards

Backtracking is which traversal over what structure?
DFS over a tree of partial solutions.
The three universal steps of backtracking
Choose → Explore (recurse) → Un-choose (undo).
Why copy path[:] when recording a solution?
path is mutated in place; storing a reference gives all answers the same (eventually empty) list.
N-Queens: how to detect same ↘ diagonal in O(1)?
All cells on it share constant r - c; keep a set of r-c.
N-Queens: key for the ↗ anti-diagonal?
Constant r + c.
Why place one queen per row in N-Queens?
Auto-removes row conflicts and cuts branching from N² to N per level.
Sudoku box index formula for cell (r,c)?
(r//3)*3 + c//3.
Why does the Sudoku solver return a bool but N-Queens does not?
Sudoku needs one solution → short-circuit on success; N-Queens collects all → explore everything.
Time/branching for all subsets vs all permutations?
Subsets 2n2^n, permutations n!n!.
What is "pruning" in backtracking?
Rejecting a partial choice early (before recursing) when it can't lead to any valid solution.
What goes wrong if you skip the un-choose step?
Sibling branches inherit stale state → wrong/duplicate results.

Connections

  • Depth-First-Search — backtracking IS DFS with explicit undo.
  • Recursion-and-the-call-stack — the implicit stack stores partial state.
  • Branch-and-Bound — pruning + a numeric bound for optimization.
  • Constraint-Satisfaction-Problems — Sudoku/N-Queens are CSPs.
  • Dynamic-Programming — when subproblems overlap, memoize the search instead.
  • Time-Complexity-of-Recursive-Algorithms — counting nodes in the search tree.

Concept Map

is

walks

node is

uses

beats

follows

requires

needs copy on record

applied to

applied to

applied to

applied to

tracks

Backtracking

DFS over solution tree

Tree of partial solutions

Early pruning

Brute force

choose explore un-choose

Shared mutated state

Subsets 2^n

Permutations n!

N-Queens

Sudoku solver

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Backtracking ka core idea simple hai: tum apna answer ek-ek choice karke banate ho, aur jaise hi pata chale ki yeh partial choice kabhi valid solution tak nahi le ja sakti, tum usko undo karke (backtrack) agla option try karte ho. Isko soch lo jaise maze solve karna — galat raaste par jaake wall mile to pure maze ko restart nahi karte, bas pichhle fork tak wapas aate ho. Yahi DFS hai with "undo" — teen magic words: Choose → Explore → Un-choose.

Subsets aur permutations sabse aasaan patterns hain. Subsets me har element ko ya to lo ya chhodo, isliye total 2n2^n. Permutations me used[] array se track karo kaun sa element use ho chuka, total n!n!. Bahut important baat: jab answer store karo to path[:] (copy) store karo, warna saare answers same list ko point karenge aur sab empty ban jayenge — yeh sabse common bug hai.

N-Queens me trick yeh hai ki har row me ek hi queen rakho, isse row-conflict apne aap khatam. Column ke liye cols set, diagonal ke liye r-c constant hota hai (↘ line), aur anti-diagonal ke liye r+c constant (↗ line). Bas in teen sets se O(1) me check ho jata hai ki square attack me hai ya nahi. Sudoku me empty cell dhoondo, 1–9 try karo, valid ho to lagao aur recurse karo; agar fail to wapas '.' kar do. Yaad rakho: pruning recurse karne se pehle karo (validity check choose se pehle), tabhi speed milti hai — warna yeh sirf brute force ban jata hai.

Go deeper — visual, from zero

Test yourself — Algorithm Paradigms

Connections