π§© Backtracking: Solving Puzzles Like a Detective
The Treasure Hunt Story
Imagine youβre in a big maze looking for treasure. You pick a path and walk forward. If you hit a dead end, what do you do? You go back to the last fork and try a different path!
Thatβs backtracking β trying a choice, and if it doesnβt work, going back and trying something else.
Think of it like this:
βββββββββββββββββββββββββββββββ
β Try something β
β β β
β Does it work? β
β β β
β YES β Keep going! β
β NO β Go back, try again β
βββββββββββββββββββββββββββββββ
π° The N-Queens Problem
The Story
You have a chessboard and N queens. Queens can attack in any direction β left, right, up, down, and diagonally. Your job? Place all N queens so no queen can attack another.
Itβs like seating N royal guests at a party where each one needs their own space!
How It Works
def solve_queens(board, col, n):
# Base case: all queens placed!
if col >= n:
return True
# Try each row in this column
for row in range(n):
if is_safe(board, row, col, n):
board[row][col] = 1 # Place queen
# Try next column
if solve_queens(board, col + 1, n):
return True
# Backtrack! Remove queen
board[row][col] = 0
return False
Visual Example (4 Queens)
Try column 0, row 0: β Place Q
[Q . . .]
[. . . .]
[. . . .]
[. . . .]
Try column 1... row 0? β (attacked)
row 1? β (attacked diagonally)
row 2? β Place Q
[Q . . .]
[. . . .]
[. Q . .]
[. . . .]
Keep going... if stuck, BACKTRACK!
The Magic Pattern
- Choose β Pick a row for the queen
- Check β Is it safe from attacks?
- Continue β Move to next column
- Backtrack β If stuck, remove queen and try another row
π’ Sudoku Solver
The Story
Sudoku is like a number puzzle party! You have a 9Γ9 grid, and every row, column, and 3Γ3 box must have numbers 1-9 exactly once. Itβs like making sure every guest at 9 tables gets exactly one slice of each flavor of cake!
How It Works
def solve_sudoku(board):
# Find empty cell
empty = find_empty(board)
if not empty:
return True # Puzzle solved!
row, col = empty
# Try numbers 1-9
for num in range(1, 10):
if is_valid(board, num, row, col):
board[row][col] = num
if solve_sudoku(board):
return True
# Backtrack!
board[row][col] = 0
return False
The Rules Check
def is_valid(board, num, row, col):
# Check row
if num in board[row]:
return False
# Check column
for r in range(9):
if board[r][col] == num:
return False
# Check 3x3 box
box_row = (row // 3) * 3
box_col = (col // 3) * 3
for r in range(box_row, box_row + 3):
for c in range(box_col, box_col + 3):
if board[r][c] == num:
return False
return True
Visual Walkthrough
Empty cell at [0,2]
Try 1: Row has 1? No β
Col has 1? No β
Box has 1? No β
Place 1!
Move to next empty...
Stuck later? Come back and try 2!
π€ Word Search in Grid
The Story
Imagine youβre playing a word hunt game. You have a grid of letters and need to find if a secret word is hiding! You can move up, down, left, or right β like a little explorer walking through letter land.
How It Works
def word_search(board, word):
rows, cols = len(board), len(board[0])
def backtrack(r, c, index):
# Found the whole word!
if index == len(word):
return True
# Out of bounds or wrong letter?
if (r < 0 or r >= rows or
c < 0 or c >= cols or
board[r][c] != word[index]):
return False
# Mark as visited
temp = board[r][c]
board[r][c] = '#'
# Try all 4 directions
found = (backtrack(r+1, c, index+1) or
backtrack(r-1, c, index+1) or
backtrack(r, c+1, index+1) or
backtrack(r, c-1, index+1))
# Backtrack: restore cell
board[r][c] = temp
return found
# Try starting from each cell
for r in range(rows):
for c in range(cols):
if backtrack(r, c, 0):
return True
return False
Visual Example
Find "CAT" in:
[C][A][T][S]
[O][G][E][N]
[D][O][G][S]
Start at C[0,0] β A[0,1] β T[0,2]
Found it! β
π Word Search II (Multiple Words)
The Story
Now youβre a super word hunter! Instead of one word, you need to find MANY words at once. The secret weapon? A Trie β a magical tree that remembers all your words!
The Trie Structure
class TrieNode:
def __init__(self):
self.children = {}
self.word = None # Store word at end
def build_trie(words):
root = TrieNode()
for word in words:
node = root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.word = word # Mark end
return root
The Search
def find_words(board, words):
root = build_trie(words)
result = []
rows, cols = len(board), len(board[0])
def backtrack(r, c, node):
char = board[r][c]
if char not in node.children:
return
next_node = node.children[char]
# Found a word!
if next_node.word:
result.append(next_node.word)
next_node.word = None # Avoid duplicates
# Mark visited
board[r][c] = '#'
# Explore neighbors
for dr, dc in [(1,0),(-1,0),(0,1),(0,-1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
if board[nr][nc] != '#':
backtrack(nr, nc, next_node)
# Backtrack
board[r][c] = char
for r in range(rows):
for c in range(cols):
backtrack(r, c, root)
return result
Why Trie?
Without Trie: Search for each word separately
= Slow! π΄
With Trie: Search all words at once
= Fast! π
Words: ["cat", "car", "card"]
root
β
c
β
a
/ \
t r
β
d
π± Letter Combinations of Phone Number
The Story
Remember old phone keypads? Each number had letters!
βββββββ¬ββββββ¬ββββββ
β 1 β 2 β 3 β
β β abc β def β
βββββββΌββββββΌββββββ€
β 4 β 5 β 6 β
β ghi β jkl β mno β
βββββββΌββββββΌββββββ€
β 7 β 8 β 9 β
βpqrs β tuv βwxyz β
βββββββ΄ββββββ΄ββββββ
If someone pressed β23β, what words could they be typing? Your job is to find ALL possibilities!
How It Works
def letter_combinations(digits):
if not digits:
return []
phone_map = {
'2': 'abc', '3': 'def',
'4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs',
'8': 'tuv', '9': 'wxyz'
}
result = []
def backtrack(index, path):
# Built full combination!
if index == len(digits):
result.append(''.join(path))
return
# Get letters for current digit
letters = phone_map[digits[index]]
# Try each letter
for letter in letters:
path.append(letter)
backtrack(index + 1, path)
path.pop() # Backtrack!
backtrack(0, [])
return result
Visual Example
Input: "23"
Digit '2' = [a, b, c]
Digit '3' = [d, e, f]
Building combinations:
a β d β "ad" β
a β e β "ae" β
a β f β "af" β
b β d β "bd" β
... and so on!
Output: ["ad","ae","af","bd","be","bf",
"cd","ce","cf"]
π― The Backtracking Recipe
Every backtracking problem follows this pattern:
ββββββββββββββββββββββββββββββββββββ
β 1. CHOOSE: Pick an option β
β β β
β 2. EXPLORE: Go deeper β
β β β
β 3. CHECK: Is it valid? β
β β β
β YES β Continue exploring β
β NO β BACKTRACK (undo) β
β β β
β 4. REPEAT until solved! β
ββββββββββββββββββββββββββββββββββββ
The Code Template
def backtrack(state):
# Base case: found solution?
if is_complete(state):
save_solution(state)
return
# Try each choice
for choice in get_choices(state):
if is_valid(choice, state):
make_choice(choice, state)
backtrack(state)
undo_choice(choice, state) # Backtrack!
π‘ Key Takeaways
| Problem | What Weβre Finding | Backtrack When |
|---|---|---|
| N-Queens | Queen positions | Queen is attacked |
| Sudoku | Number placements | Number violates rules |
| Word Search | Word path | Wrong letter or visited |
| Word Search II | Multiple words | No matching prefix |
| Phone Letters | Letter combinations | Built full string |
Remember: Backtracking is just smart trial and error. Try something, check if it works, and if not β go back and try something else! π
