name: Backtracking slug: backtracking difficulty_level: 3 pattern_type: algorithm display_order: 10 description: > Build solutions incrementally, exploring choices one at a time and abandoning paths that fail to satisfy constraints. Backtracking systematically explores all possibilities by making a choice, recursing, then undoing the choice. when_to_use: | - Generating all permutations or combinations - Constraint satisfaction (Sudoku, N-Queens) - Subset/partition problems - Path finding with constraints - Word search in grids metaphor: | Imagine navigating a maze by always trying the left path first. When you hit a dead end, you backtrack to the last intersection and try the next option. You systematically explore every possible route, backing up whenever you reach an invalid state. Another analogy: filling out a Sudoku puzzle. You try a number in an empty cell. If it leads to a contradiction later, you erase it (backtrack) and try the next number. If no number works, you backtrack further to a previous cell. core_concept: | Backtracking is a refined brute force that prunes invalid branches early. The pattern follows a template: 1. **Choose**: Make a choice (add element to path, place a queen, etc.) 2. **Explore**: Recurse to explore consequences of that choice 3. **Unchoose**: Undo the choice (backtrack) to try alternatives The key insight is that we build a **decision tree** where each node represents a state and edges represent choices. Backtracking is a DFS of this tree, with pruning of subtrees that can't lead to valid solutions. **Pruning** is what makes backtracking efficient. By detecting invalid states early, we avoid exploring exponentially many useless branches. visualization: | **Generating subsets of [1, 2, 3]:** ``` Decision tree (include or exclude each element): [] / \ [1] [] / \ / \ [1,2] [1] [2] [] / \ / \ / \ / \ [1,2,3][1,2][1,3][1][2,3][2][3][] Subsets: [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ``` **N-Queens (N=4) with pruning:** ``` Place queens row by row, checking column and diagonal conflicts: Row 0: Try col 0 Row 1: col 0 ❌ (same col) col 1 ❌ (diagonal) col 2 ✓ Row 2: col 0 ❌ (diagonal) col 1 ❌ (col 1 attacks via diagonal) col 2 ❌ (same col) col 3 ❌ (diagonal) Backtrack! Row 1: col 3 ✓ Row 2: col 1 ✓ Row 3: col 0 ❌ col 1 ❌ col 2 ❌ col 3 ❌ Backtrack! ... Eventually find: [1, 3, 0, 2] (queens at cols 1,3,0,2 for rows 0,1,2,3) ``` **Template visualization:** ``` backtrack(state): if is_solution(state): record_solution(state) return for choice in get_choices(state): if is_valid(choice, state): ← PRUNE make_choice(choice, state) ← CHOOSE backtrack(state) ← EXPLORE undo_choice(choice, state) ← UNCHOOSE ``` code_template: | def generate_subsets(nums: list[int]) -> list[list[int]]: """Generate all subsets (power set).""" result = [] def backtrack(start: int, path: list[int]): result.append(path[:]) # Record current subset for i in range(start, len(nums)): path.append(nums[i]) # Choose backtrack(i + 1, path) # Explore path.pop() # Unchoose backtrack(0, []) return result def generate_permutations(nums: list[int]) -> list[list[int]]: """Generate all permutations.""" result = [] used = [False] * len(nums) def backtrack(path: list[int]): if len(path) == len(nums): result.append(path[:]) return for i in range(len(nums)): if used[i]: continue used[i] = True # Choose path.append(nums[i]) backtrack(path) # Explore path.pop() # Unchoose used[i] = False backtrack([]) return result def combination_sum(candidates: list[int], target: int) -> list[list[int]]: """Find combinations that sum to target (can reuse elements).""" result = [] def backtrack(start: int, path: list[int], remaining: int): if remaining == 0: result.append(path[:]) return for i in range(start, len(candidates)): if candidates[i] > remaining: # Prune continue path.append(candidates[i]) backtrack(i, path, remaining - candidates[i]) # Can reuse path.pop() candidates.sort() # Enable pruning backtrack(0, [], target) return result def solve_n_queens(n: int) -> list[list[str]]: """Find all valid N-Queens solutions.""" result = [] cols = set() diag1 = set() # row - col diag2 = set() # row + col def backtrack(row: int, queens: list[int]): if row == n: # Convert to board representation board = [] for c in queens: board.append('.' * c + 'Q' + '.' * (n - c - 1)) result.append(board) return for col in range(n): if col in cols or (row - col) in diag1 or (row + col) in diag2: continue # Prune: under attack # Choose cols.add(col) diag1.add(row - col) diag2.add(row + col) queens.append(col) backtrack(row + 1, queens) # Explore # Unchoose queens.pop() cols.remove(col) diag1.remove(row - col) diag2.remove(row + col) backtrack(0, []) return result def word_search(board: list[list[str]], word: str) -> bool: """Check if word exists in grid following adjacent cells.""" rows, cols = len(board), len(board[0]) def backtrack(r: int, c: int, i: int) -> bool: if i == len(word): return True if (r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[i]): return False # Choose: mark as visited temp = board[r][c] board[r][c] = '#' # Explore neighbors found = (backtrack(r + 1, c, i + 1) or backtrack(r - 1, c, i + 1) or backtrack(r, c + 1, i + 1) or backtrack(r, c - 1, i + 1)) # Unchoose: restore board[r][c] = temp return found for r in range(rows): for c in range(cols): if backtrack(r, c, 0): return True return False recognition_signals: - "all permutations" - "all combinations" - "all subsets" - "generate all" - "N-Queens" - "Sudoku" - "word search" - "partition" - "constraint satisfaction" - "valid arrangements" common_mistakes: - title: Forgetting to unchoose (backtrack) description: | Not undoing the choice after exploring leaves state modified, causing incorrect results for subsequent branches. fix: | Always pair choose with unchoose: ```python path.append(choice) # Choose backtrack(...) # Explore path.pop() # Unchoose - MUST DO! ``` - title: Modifying state without copying description: | Adding the same path object to results multiple times—they all reference the same list that keeps changing. fix: | Copy the path when recording: ```python result.append(path[:]) # Shallow copy # or result.append(list(path)) ``` - title: Not pruning effectively description: | Checking validity only at leaf nodes means exploring many invalid branches that could have been cut early. fix: | Validate as early as possible: ```python for choice in choices: if is_valid(choice): # Prune BEFORE recursing make_choice(choice) backtrack(...) ``` - title: Wrong base case description: | Recording partial solutions as complete, or not recognizing when a complete solution is reached. fix: | Clearly define what constitutes a complete solution: - Permutations: path length equals input length - Subsets: (all indices are complete solutions) - N-Queens: placed N queens variations: - name: Subsets description: | Generate all subsets. Each element is either included or not. Record at every node, not just leaves. example: "Subsets, Subsets II (with duplicates)" - name: Permutations description: | Generate all orderings. Track which elements are used. Record only at leaves (when all elements used). example: "Permutations, Permutations II" - name: Combinations description: | Generate subsets of specific size k, or combinations that meet a target sum. Use start index to avoid duplicates. example: "Combinations, Combination Sum" - name: Constraint satisfaction description: | Place elements satisfying constraints (non-attacking queens, valid Sudoku). Heavy pruning based on constraints. example: "N-Queens, Sudoku Solver" - name: Path finding description: | Find paths in grids or graphs, marking visited cells temporarily. Unmark when backtracking. example: "Word Search, Unique Paths III" related_patterns: - dfs - dynamic-programming prerequisite_patterns: - dfs