Solve 12 classic backtracking interview problems — word search, letter combinations of phone number, palindrome partitioning, restore IP addresses, permutations II with duplicates, combination sum II, beautiful arrangements, word break II, expression add operators, and more.
Problem 1 — Word Search in 2D Grid
Problem: Given an m×n grid of characters and a word, return True if the word exists in the grid. The word can be constructed from letters of sequentially adjacent cells (horizontally or vertically adjacent).
def word_search(board, word):
"""
DFS/Backtracking on grid to find word.
Time: O(m × n × 4^L) where L = len(word)
"""
m, n = len(board), len(board[0])
def dfs(r, c, idx):
if idx == len(word):
return True # All characters matched
if r < 0 or r >= m or c < 0 or c >= n or board[r][c] != word[idx]:
return False
# CHOOSE: Mark as visited
temp = board[r][c]
board[r][c] = '#'
# EXPLORE all 4 directions
found = (dfs(r+1, c, idx+1) or dfs(r-1, c, idx+1) or
dfs(r, c+1, idx+1) or dfs(r, c-1, idx+1))
# UNDO: Restore original character
board[r][c] = temp
return found
for r in range(m):
for c in range(n):
if dfs(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
Problem 3 — Palindrome Partitioning
Problem: Partition string s such that every substring is a palindrome. Return all such partitionings.
def partition(s):
"""
Find all palindrome partitionings of string s.
Time: O(n × 2^n), Space: O(n)
"""
result = []
def is_palindrome(sub):
return sub == sub[::-1]
def backtrack(start, current):
if start == len(s):
result.append(list(current))
return
for end in range(start + 1, len(s) + 1):
substring = s[start:end]
if is_palindrome(substring):
current.append(substring)
backtrack(end, current)
current.pop()
backtrack(0, [])
return result
print(partition("aab"))
# [['a','a','b'], ['aa','b']]
print(partition("racecar"))
# [['r','a','c','e','c','a','r'], ['r','aceca','r'], ['racecar']]
Problem 4 — Restore IP Addresses
def restore_ip_addresses(s):
"""
Restore all valid IP addresses from digit string.
Each segment: 0-255, no leading zeros.
Time: O(3^4 × |s|) = O(|s|)
"""
result = []
def backtrack(start, segments):
if len(segments) == 4:
if start == len(s):
result.append('.'.join(segments))
return
# Each segment can be 1-3 digits
for length in range(1, 4):
if start + length > len(s):
break
segment = s[start:start + length]
# No leading zeros (except "0" itself)
if len(segment) > 1 and segment[0] == '0':
break
# Segment value must be 0-255
if int(segment) > 255:
break
segments.append(segment)
backtrack(start + length, segments)
segments.pop()
backtrack(0, [])
return result
print(restore_ip_addresses("25525511135")) # ["255.255.11.135","255.255.111.35"]
print(restore_ip_addresses("0000")) # ["0.0.0.0"]
print(restore_ip_addresses("1111111111111111")) # []
Problem 5 — Permutations II (With Duplicates)
def permutations_with_dups(nums):
"""
All unique permutations when nums contains duplicates.
Sort first, then skip duplicate elements at same position.
"""
nums.sort()
result = []
used = [False] * len(nums)
def backtrack(current):
if len(current) == len(nums):
result.append(list(current))
return
for i in range(len(nums)):
if used[i]:
continue
# Skip duplicate: if nums[i]==nums[i-1] and nums[i-1] not used,
# this would create a duplicate permutation
if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
continue
used[i] = True
current.append(nums[i])
backtrack(current)
current.pop()
used[i] = False
backtrack([])
return result
print(len(permutations_with_dups([1,1,2]))) # 3 (not 6)
print(permutations_with_dups([1,1,2]))
# [[1,1,2],[1,2,1],[2,1,1]]
Problem 6 — Combination Sum II (No Reuse, With Duplicates)
def combination_sum_2(candidates, target):
"""
Find all unique combinations summing to target.
Each candidate used at most once. May have duplicates.
"""
candidates.sort()
result = []
def backtrack(start, current, remaining):
if remaining == 0:
result.append(list(current))
return
for i in range(start, len(candidates)):
if candidates[i] > remaining:
break # Pruning
# Skip duplicates at same level
if i > start and candidates[i] == candidates[i-1]:
continue
current.append(candidates[i])
backtrack(i + 1, current, remaining - candidates[i]) # i+1 = no reuse
current.pop()
backtrack(0, [], target)
return result
print(combination_sum_2([10,1,2,7,6,1,5], 8))
# [[1,1,6],[1,2,5],[1,7],[2,6]]
Problem 7 — Beautiful Arrangement
def count_arrangement(n):
"""
Count permutations of 1..n where position i satisfies:
nums[i] % i == 0 OR i % nums[i] == 0
"""
visited = [False] * (n + 1)
count = [0]
def backtrack(pos):
if pos > n:
count[0] += 1
return
for num in range(1, n + 1):
if not visited[num] and (num % pos == 0 or pos % num == 0):
visited[num] = True
backtrack(pos + 1)
visited[num] = False
backtrack(1)
return count[0]
print(count_arrangement(2)) # 2
print(count_arrangement(3)) # 3
print(count_arrangement(15)) # 24
Problem 8 — Generate Parentheses
def generate_parentheses(n):
"""
Generate all valid combinations of n pairs of parentheses.
"""
result = []
def backtrack(s, open_count, close_count):
if len(s) == 2 * n:
result.append(s)
return
if open_count < n:
backtrack(s + '(', open_count + 1, close_count)
if close_count < open_count:
backtrack(s + ')', open_count, close_count + 1)
backtrack("", 0, 0)
return result
print(generate_parentheses(3))
# ['((()))', '(()())', '(())()', '()(())', '()()()']
Problem 9 — Word Break II
def word_break_2(s, word_dict):
"""
Return all ways to break s into valid dictionary words.
"""
word_set = set(word_dict)
result = []
def backtrack(start, current):
if start == len(s):
result.append(' '.join(current))
return
for end in range(start + 1, len(s) + 1):
word = s[start:end]
if word in word_set:
current.append(word)
backtrack(end, current)
current.pop()
backtrack(0, [])
return result
print(word_break_2("catsanddog", ["cat","cats","and","sand","dog"]))
# ["cats and dog","cat sand dog"]
Problem 10 — Expression Add Operators
def add_operators(num, target):
"""
Add +, -, * operators between digits to reach target value.
"""
result = []
def backtrack(idx, path, value, prev):
if idx == len(num):
if value == target:
result.append(path)
return
for i in range(idx, len(num)):
# No leading zeros
if i != idx and num[idx] == '0':
break
curr_str = num[idx:i+1]
curr = int(curr_str)
if idx == 0:
backtrack(i+1, curr_str, curr, curr)
else:
backtrack(i+1, path+'+'+curr_str, value+curr, curr)
backtrack(i+1, path+'-'+curr_str, value-curr, -curr)
# For *, undo prev and apply multiply
backtrack(i+1, path+'*'+curr_str, value-prev+prev*curr, prev*curr)
backtrack(0, "", 0, 0)
return result
print(add_operators("123", 6)) # ["1+2+3","1*2*3"]
print(add_operators("232", 8)) # ["2+3*2","2*3+2"]
print(add_operators("3456237490", 9191)) # []
Problem 11 — Sudoku Valid State Check (Sub-problem)
def is_valid_sudoku(board):
"""Check if a partially filled Sudoku board is valid."""
rows = [set() for _ in range(9)]
cols = [set() for _ in range(9)]
boxes = [set() for _ in range(9)]
for r in range(9):
for c in range(9):
val = board[r][c]
if val == '.':
continue
box = (r//3)*3 + c//3
if val in rows[r] or val in cols[c] or val in boxes[box]:
return False
rows[r].add(val)
cols[c].add(val)
boxes[box].add(val)
return True
Problem 12 — Graph Coloring
def can_color_graph(graph, n, m):
"""
Can you color n vertices with m colors such that no adjacent vertices share a color?
"""
colors = [0] * (n + 1) # colors[i] = color of vertex i (0 = uncolored)
def is_safe(vertex, color):
for neighbor in graph[vertex]:
if colors[neighbor] == color:
return False
return True
def backtrack(vertex):
if vertex > n:
return True # All vertices colored
for color in range(1, m + 1):
if is_safe(vertex, color):
colors[vertex] = color
if backtrack(vertex + 1):
return True
colors[vertex] = 0
return False
return backtrack(1)
# Example: 4-colorable graph
graph = {1: [2,3], 2: [1,3,4], 3: [1,2,4], 4: [2,3]}
print(can_color_graph(graph, 4, 3)) # True — 3 colors sufficient
print(can_color_graph(graph, 4, 2)) # False — need at least 3 colors
Pattern Recognition Summary
| Problem Pattern | Key Technique | Example |
|---|
| Grid traversal | Mark/unmark visited | Word Search |
| String building | Start index progression | Palindrome partition |
| Phone combinations | Multi-char expansion | Letter combinations |
| IP addresses | Length-limited segments | Restore IP |
| Permutations w/ dups | Sort + skip same-level | Permutations II |
| Valid arrangements | Constraint checking | Beautiful arrangement |
| Expression building | Track value + prev | Add operators |
| Graph constraints | Color assignment | Graph coloring |
Backtracking solves all these because they share the same structure: build incrementally, validate, backtrack when invalid.
*Backtracking section complete.*