Word Search II
Find which words from a list exist as adjacent-cell paths in a letter grid (no cell reused per word).
Open on LeetCode ↗Intuition
Running one grid-DFS per word repeats the same walks over and over. Instead put ALL words into a trie and run one DFS per grid cell, moving through grid and trie together: a step is only taken if the trie has that child, so the search dies the instant no word can match. Hitting a word-flag records a hit — the trie searches every word simultaneously.
One trie walked alongside the grid DFS, instead of running Word Search once per word. The trie collapses shared prefixes so a dead end kills every word beneath it at once. Popping the "$" marker on a hit is a neat de-duplication trick — the word can never be reported twice.
Approach
Trie of all words
Insert every word; store the full word at its end node (no separate flag needed — the word itself is the flag).
DFS grid and trie in lockstep
From each cell whose letter is a trie child of the root: mark the cell visited, recurse into neighbors whose letters continue the trie path, unmark on return. Missing child = instant prune.
Dedupe and prune harder
On finding a word, null its marker so it is reported once. Optionally delete leaf trie nodes after exhausting them — the trie shrinks as words are found, speeding later cells.
Solution & live demo
Common pitfalls
Searching each word independently
for w in words:
if exist(board, w): res.append(w)# one trie, one DFS over the grid
With thousands of words sharing prefixes, that repeats the same failing exploration for every one of them. The trie makes the grid pay for each distinct prefix once, no matter how many words start with it.
Returning instead of continuing after a match
if w: res.append(w); return
if w: res.append(w) # keep exploring
A matched word may be the prefix of a longer one also present in the grid. Returning at the first hit stops the walk and silently drops those longer words.
Forgetting to restore the cell
board[r][c] = "#" for ...: dfs(...)
board[r][c] = "#" for ...: dfs(...) board[r][c] = ch
The # marks the cell as in-use for the current path only. Leaving it set makes the cell permanently unusable, so words needing it via a different route are never found.
Edge cases
Clearing the end-node marker on first find prevents duplicates.
Interior end-markers fire without stopping the deeper search.
Temporary '#' marking blocks revisits; restored on backtrack.