BACKTRACKING ============ Backtracking explores all possible solutions by building candidates incrementally. If a candidate fails, it backtracks and tries the next option. Template: def backtrack(state, choices): if is_solution(state): record(state) return for choice in choices: if is_valid(state, choice): make_choice(state, choice) backtrack(state, remaining_choices) undo_choice(state, choice) # BACKTRACK --- Generate All Subsets --- def subsets(nums): result = [] def backtrack(start, current): result.append(current[:]) # record every state for i in range(start, len(nums)): current.append(nums[i]) backtrack(i + 1, current) current.pop() # backtrack backtrack(0, []) return result print(subsets([1, 2, 3])) # [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]] --- Generate All Permutations --- def permute(nums): result = [] def backtrack(current): if len(current) == len(nums): result.append(current[:]) return for num in nums: if num not in current: current.append(num) backtrack(current) current.pop() # backtrack backtrack([]) return result print(permute([1, 2, 3])) # [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]] --- Combination Sum --- Find all combinations that sum to target. Numbers can be reused. def combination_sum(candidates, target): result = [] candidates.sort() def backtrack(start, current, remaining): if remaining == 0: result.append(current[:]) return for i in range(start, len(candidates)): if candidates[i] > remaining: break current.append(candidates[i]) backtrack(i, current, remaining - candidates[i]) # i (not i+1) allows reuse current.pop() backtrack(0, [], target) return result print(combination_sum([2, 3, 6, 7], 7)) # [[2, 2, 3], [7]] --- N-Queens Problem --- Place N queens on NxN board so no two queens attack each other. def solve_n_queens(n): result = [] cols = set() diag1 = set() # row - col diag2 = set() # row + col board = [['.' for _ in range(n)] for _ in range(n)] def backtrack(row): if row == n: result.append([''.join(r) for r in board]) return for col in range(n): if col in cols or (row - col) in diag1 or (row + col) in diag2: continue cols.add(col) diag1.add(row - col) diag2.add(row + col) board[row][col] = 'Q' backtrack(row + 1) cols.remove(col) # backtrack diag1.remove(row - col) diag2.remove(row + col) board[row][col] = '.' backtrack(0) return result solutions = solve_n_queens(4) print(f"Number of solutions for 4-Queens: {len(solutions)}") # 2 for sol in solutions: for row in sol: print(row) print() --- Sudoku Solver --- Fill 9x9 grid so each row, column, and 3x3 box has digits 1-9. def solve_sudoku(board): def is_valid(board, row, col, num): num = str(num) if num in board[row]: return False if num in [board[r][col] for r in range(9)]: return False box_r, box_c = 3 * (row // 3), 3 * (col // 3) for r in range(box_r, box_r + 3): for c in range(box_c, box_c + 3): if board[r][c] == num: return False return True def backtrack(): for r in range(9): for c in range(9): if board[r][c] == '.': for num in range(1, 10): if is_valid(board, r, c, num): board[r][c] = str(num) if backtrack(): return True board[r][c] = '.' # backtrack return False return True backtrack() --- Word Search in Grid --- Find if word exists in 2D grid by moving up/down/left/right. def word_search(board, word): rows, cols = len(board), len(board[0]) def backtrack(r, c, index): if index == len(word): return True if r < 0 or r >= rows or c < 0 or c >= cols: return False if board[r][c] != word[index]: return False temp = board[r][c] board[r][c] = '#' # mark visited 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)) board[r][c] = temp # backtrack (restore) return found for r in range(rows): for c in range(cols): if backtrack(r, c, 0): return True return False board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]] print(word_search(board, "ABCCED")) # True print(word_search(board, "SEE")) # True print(word_search(board, "ABCB")) # False --- Rat in a Maze --- Find path from (0,0) to (n-1,n-1) in a grid (1=open, 0=blocked). def rat_in_maze(maze): n = len(maze) solution = [[0]*n for _ in range(n)] def is_safe(r, c): return 0 <= r < n and 0 <= c < n and maze[r][c] == 1 def solve(r, c): if r == n-1 and c == n-1: solution[r][c] = 1 return True if is_safe(r, c): solution[r][c] = 1 if solve(r+1, c): return True if solve(r, c+1): return True solution[r][c] = 0 # backtrack return False return False if solve(0, 0): return solution return [] maze = [[1,0,0,0],[1,1,0,1],[0,1,0,0],[1,1,1,1]] path = rat_in_maze(maze) for row in path: print(row)