Skip to content
mlmentorship

Word Search II

Find every dictionary word that can be formed on a letter board.

Published · 7 min read ·Specialist ·Advanced

30-second answer map

Visual first · depth when needed

Follow board cells and trie children together, mark the current path in place, emit terminal words once, and prune exhausted trie branches while backtracking.

Preparing the visual…

ML breadth · active recall

Practice before you read

8 minutes. Explain the mechanism, why it works, when it fails, and one alternative.

How practice works

ML breadth · closed-book attempt

Word Search II

Explain the mechanism, why it works, when it fails, and one alternative.

08:00recommended time

Closing or reloading clears the scratchpad. Only score, weak rubric dimensions, attempt count, and retry date can be stored locally.

Find every dictionary word that can be formed on a letter board.

Start with the concrete trace below. It shows the state the algorithm must carry as it runs.

Problem trace

Word Search II: Follow board cells and trie children together, mark the current path in place, emit terminal words once, and prune exhausted trie branches while backtracking.

Input and goalFind every dictionary word that can be formed on a letter board.
Build the shared prefix trieInsert words oat, oath, eat, and pea. oat is terminal at t while oath continues from that same t to h.
Shared-prefix trieOne node per shared prefix; double borders mark complete words.oepaaettahcurrentrootoepoaeapeoateatpeaoath

double border complete word

board[[o,a,t],[e,t,h],[e,a,t]]terminalsdouble borders mark oat, oath, eat, and pea

Recognize it
Use a trie when many dictionary words must be searched on the same board and their prefixes can share traversal work.
Keep true
At each recursive call, the marked board path contains distinct adjacent cells spelling exactly the trie path to node; every emitted terminal has been removed, and every pruned trie branch can no longer produce an unseen word.
Reuse it
When many searches share prefixes, traverse input and trie in lockstep, encode path-local visited state reversibly, remove consumed outputs, and prune nodes only after their subtree is exhausted.
Read it this way: Insert words oat, oath, eat, and pea. oat is terminal at t while oath continues from that same t to h. Step through the frames to watch the state change. The last frame shows the answer or the stopping condition.

Pattern: Trie plus grid backtracking.

Simple idea: Word Search starts a separate search for one word. A trie lets all words share the same search. Stop when the current board path is not a dictionary prefix.

def _make_word_trie(words: list[str]) -> dict:
   trie: dict = {}
   for word in words:
      node = trie
      for char in word:
         node = node.setdefault(char, {})
      node[None] = word
   return trie


def _find_words_from(
   board: list[list[str]], row: int, col: int, node: dict, answer: list[str]
) -> None:
   char = board[row][col]
   if char not in node:
      return

   next_node = node[char]
   word = next_node.pop(None, None)
   if word:
      answer.append(word)

   board[row][col] = "#"
   for row_step, col_step in ((1, 0), (-1, 0), (0, 1), (0, -1)):
      next_row = row + row_step
      next_col = col + col_step
      if 0 <= next_row < len(board) and 0 <= next_col < len(board[0]):
         _find_words_from(board, next_row, next_col, next_node, answer)
   board[row][col] = char

   if not next_node:
      node.pop(char)


def find_words(board: list[list[str]], words: list[str]) -> list[str]:
   trie = _make_word_trie(words)
   answer: list[str] = []

   for row in range(len(board)):
      for col in range(len(board[0])):
         _find_words_from(board, row, col, trie, answer)
   return answer

Cost: The trie takes space. Search time depends on the board and shared prefixes. Prefix checks remove most impossible paths early.