Word Search
Word Search is LeetCode 79 (Medium). You get an m × n grid of letters, board, and a string word. Return true if the word can be spelled by a path through the grid, and false otherwise.
The rules for the path:
- It starts on any cell holding the word's first letter.
- Each next letter must sit in a cell horizontally or vertically adjacent to the previous one. Diagonal steps are not allowed.
- No cell may be used twice in the same path.
You only need to report whether such a path exists, not where it is or how many there are.
- m == board.length
- n = board[i].length
- 1 <= m, n <= 6
- 1 <= word.length <= 15
- board and word consists of only lowercase and uppercase English letters.
Intuition
In the word search LeetCode problem, a path is a chain of choices: from each cell there are up to four places to go next. Depth-first search walks that tree of choices one branch at a time, and a wrong letter kills a branch at once, which keeps the search fast in practice. That is why word search backtracking is the standard answer.
The no cell twice rule is a property of one path, not of the board. So a cell is marked while it is on the current path and unmarked the moment the search leaves it: mark on the way in, restore on the way out. Another branch or another start may need that same letter later.
A grid, a sequence to trace through it, and a ban on reusing cells within one path. That is word search grid DFS: depth-first because a path is built one step at a time and abandoned as a unit, with a per-path visited mark that is undone on return. Flood fill (Number of Islands) marks cells and never unmarks them, because there a cell belongs to one island for good; here a cell belongs to a path only while that path is alive.
Approach
Before reading on, decide what the recursive call needs to know, what it rejects immediately, and exactly when a cell's mark is removed. Aim for O(m·n·3^L) time and O(L) extra space, where L is the word length.
Try every cell as a starting point
Scan the grid in row order and start a search from each cell. A cell that does not hold word[0] fails the first check at once, so this costs O(1) per wrong cell. Return true as soon as one start succeeds.
Reject first, then recurse
dfs(r, c, k) asks: can word[k:] be spelled starting at (r, c)? Return false straight away if the cell is off the grid or does not hold word[k]; a cell marked '#' fails this same test. If k is the last index, the whole word is matched: return true.
Mark, try four neighbours, restore
- Write
'#'into the cell so this path cannot reuse it. - Call
dfson the cells up, down, left and right withk + 1, stopping at the firsttrue. - Write
word[k]back into the cell before returning, whatever the result.
The board doubles as the visited set, so no extra memory is needed beyond the recursion stack.
Prune before searching (optional speed-ups)
- If the word is longer than
m × n, returnfalsewithout searching. - Count letters: if the board has fewer copies of some letter than the word needs, no path can exist.
- If the word's last letter is rarer on the board than its first, reverse the word. Fewer starting cells means fewer doomed searches.
Word Search solution in Python | C++ | Java
Common pitfalls
Marking a cell and never restoring it
board[r][c] = "#" found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in DIRS) return found
board[r][c] = "#" found = any(dfs(r + dr, c + dc, k + 1) for dr, dc in DIRS) board[r][c] = word[k] return found
On the sample board, "CCB" first starts at the top C, which fails. Without the restore that C stays '#', and the second start, which needs it as its second letter, is rejected. The answer comes out false although the word is there.
Searching with BFS and one shared visited set
seen = {(r, c)}
q = deque([(r, c, 0)])
while q:
... # mark each neighbour in seen when queueddef dfs(r, c, k):
... # mark on entry, unmark on returnA cell can be reached by several partial paths, and only one of them may be the right one. BFS keeps one global seen, so the first partial path to claim a cell blocks every other path from using it. The no reuse rule is per path, and only DFS has a single current path to attach the mark to.
Edge cases
Success is detected on the cell that matches the last letter (k == len(word) - 1), not by stepping into a neighbour afterwards. A 1 × 1 board has no neighbour to step into, so the other order would return false for a one-letter word that is sitting right there.
Complexity
Word Search vs look-alike grid problems
The grid and the DFS look the same in all three. What differs is how many words there are and whether a mark is ever undone.
| Problem | Question | Visited mark | Tool |
|---|---|---|---|
| Word Search (79) | Is this one word on the board? | Per path: set on entry, undone on return | DFS + backtracking |
| Word Search II (212) | Which of many words are on the board? | Per path, same as 79 | DFS guided by a trie of all words |
| Number of Islands (200) | How many connected land regions? | Permanent: a cell joins one island for good | Flood fill, no undo |
Word Search FAQ
How do you write word search in Python?
Define a nested dfs(r, c, k) that returns False for an out-of-range or mismatched cell, True when k is the last index, and otherwise marks the cell, uses any(...) over the four directions, restores the cell and returns the result. The outer loops call dfs(r, c, 0) for every cell. any stops at the first True, so no extra early-exit code is needed in word search in Python.