Word Search
Given an m x n grid of characters and a string word, return true if the word can be traced on the grid.
A trace is a path of sequentially adjacent cells — horizontally or vertically neighbouring, never diagonally — whose letters spell the word in order. No cell may be used more than once within the same trace.
Only a boolean is required; the path itself is not returned. The search may begin at any cell.
- m == board.length, n == board[i].length
- 1 <= m, n <= 6
- 1 <= word.length <= 15
- board and word consist of English letters
- Matching is case-sensitive: 'A' and 'a' are different characters
board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"trueboard = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"trueboard = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"falseboard = [["a"]], word = "a"trueTrace a word through the grid, stepping to up/down/left/right neighbours, never reusing a cell in the same path. Same template — but we want a yes/no answer, so the recursion returns True/False and stops at the first match. The shared state we change is the board itself: mark a cell while the path is on it, unmark it on the way out.
Every problem in this section is the same loop — three beats: choose, explore, un-choose.
def backtrack(state):
if done(state):
save(state) # record the finished answer
return
for choice in choices(state): # what can I pick right now?
if not ok(choice):
continue # prune: skip bad picks early
apply(choice) # CHOOSE
backtrack(next_state) # EXPLORE
undo(choice) # UN-CHOOSE: put it backOnly four slots change between problems: done, choices, ok (pruning), and how you finish (save every answer, or return True at the first one). Fill those and the problem is solved.
def exist(board, word):
rows, cols = len(board), len(board[0])
def backtrack(r, c, i):
if i == len(word):
return True # whole word matched
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != word[i]:
return False # PRUNE: off-grid / wrong / used
board[r][c] = '#' # CHOOSE: mark used
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)) # EXPLORE
board[r][c] = word[i] # UN-CHOOSE: restore
return found
for r in range(rows):
for c in range(cols):
if backtrack(r, c, 0):
return True
return FalseRestore the cell even when found is True — the board belongs to the caller, so leave it clean. The '#' mark is what enforces "no reuse in this path": a neighbour already marked '#' fails the letter test.
Trace "ABCB": A → B → C, then the last B needs the B cell again, but it's '#' now → fails → the whole trace returns false.
Word Search (DFS)
MINDSET
We explore the grid like a graph. Every cell is a node, and every adjacent cell is an edge. We only traverse an edge if the character matches the next one in our target word.
BACKTRACKING
We must mark cells as 'visited' during a path to avoid cycles, but we MUST unmark them (backtrack) so they can be reused in different branches.