AlgoMaster Logo

Word Search II

hardFrequency7 min readUpdated June 23, 2026

Understanding the Problem

This is an extension of the classic Word Search problem, but instead of checking one word at a time, we need to find all words from a given list that exist on the board. A naive approach would be to run Word Search I for each word individually, but with up to 30,000 words, that repeats the full board search 30,000 times.

The real question is whether we can share work across multiple word searches. If two words share a common prefix (like "oath" and "oat"), tracing the same path twice to check both words is wasted effort. A Trie (prefix tree) solves this. By inserting all words into a Trie first, we can explore the board once and check against all words at the same time as we traverse.

Key Constraints:

  • 1 <= m, n <= 12 -> The board has at most 144 cells, small enough for backtracking with pruning.
  • 1 <= words.length <= 3 * 10^4 -> Up to 30,000 words. Searching for each word individually multiplies the board traversal by 30,000, which is the main cost to eliminate.
  • 1 <= words[i].length <= 10 -> Words are at most 10 characters, which bounds the backtracking depth at 10.
  • All strings in words are unique -> The word list has no duplicates, but a single word can still be spelled by more than one board path, so the result must avoid adding it twice.

Approach 1: Brute Force (Word Search I for Each Word)

Intuition

Reuse the solution from Word Search I. For each word in the list, iterate over every cell on the board and run a DFS/backtracking search to see if that word exists. If it does, add it to the result.

This is correct but does a lot of redundant work. If the words "oath" and "oat" are both in the list, the path O -> A -> T gets traced twice, once for each word. With 30,000 words, the full board search runs 30,000 times.

Algorithm

  1. For each word in the word list, run Word Search I.
  2. For Word Search I: iterate over every cell (r, c) on the board. If board[r][c] matches the first character of the word, start a DFS.
  3. In the DFS, mark the current cell as visited, then try all 4 neighbors. If we match all characters of the word, return true.
  4. Backtrack by restoring the cell's original value.
  5. Collect all words that return true.

Example Walkthrough

Input:

0
1
2
3
0
o
a
a
n
1
e
t
a
e
2
i
h
k
r
3
i
f
l
v
board
0
oath
1
pea
2
eat
3
rain
words

We run a separate DFS for each word in list order.

  • "oath": The only 'o' is at (0,0). From there, 'a' is at (0,1), 't' is at (1,1) below it, and 'h' is at (2,1) below that. All four characters match along adjacent cells, so "oath" is found and added to the result.
  • "pea": No cell on the board holds 'p', so every starting scan fails. Not found.
  • "eat": Scanning for 'e', the cell (1,0)='e' has no adjacent 'a', so that start fails. The next 'e' is at (1,3). From there, 'a' is at (1,2) and 't' is at (1,1). Match. "eat" is added.
  • "rain": No cell holds 'r' adjacent to a path spelling "rain". The only 'r' is at (2,3), whose neighbors are 'k', 'e', and 'v', none of which is 'a'. Not found.

The result, in the order words were processed, is ["oath", "eat"].

0
oath
1
eat
result

Code

This runs a full board DFS for every word in the list, retracing shared prefixes once per word. The next approach searches for all words at once: a single DFS from a cell checks against every word at the same time.

Approach 2: Trie + Backtracking (Optimal)

Intuition

Flip the problem. Instead of taking each word and searching the board for it, explore the board and check whether the current path matches any word. A Trie makes this efficient because at each DFS step we follow the corresponding child pointer in the Trie. If no child exists for the current board character, that branch is pruned immediately, since no word in the list starts with this prefix.

Insert all words into a Trie. Then, for each cell on the board, start a DFS. At each cell, check whether the Trie node has a child matching that character. If it does, move to that child and continue exploring in all 4 directions. If the current Trie node marks the end of a word, that word is a match and gets added to the results.

Two optimizations keep this fast:

  1. Remove found words from the Trie. Once "oath" is found, remove it from the Trie. This prevents adding duplicates and lets future DFS explorations prune earlier, since there are fewer words left to match against.
  2. Prune empty branches. After removing a word, if a Trie node has no remaining children, delete it. This stops the DFS from exploring paths that lead nowhere.

Algorithm

  1. Build a Trie from all words in the list.
  2. For each cell (r, c) on the board, start a DFS with the root of the Trie.
  3. At each cell during DFS:
    • If the cell is out of bounds, already visited, or the Trie node has no child for this character, return.
    • Move to the child node in the Trie.
    • If this node stores a complete word, add it to results and remove the word from the node (set it to null).
    • Mark the cell as visited, recurse in all 4 directions, then restore the cell (backtrack).
    • If the current Trie node has no children left after recursion, remove it from its parent (pruning).
  4. Return the collected results.

Example Walkthrough

1Build Trie with words: oath, pea, eat, rain
rootoperaeaatatihn
1/9
1Build Trie with words: oath, pea, eat, rain
0
1
2
3
0
o
a
a
n
1
e
t
a
e
2
i
h
k
r
3
i
f
l
v
1/9

Code