◀ THE GRIND — TRIES

Word Search II

The drill: A letter grid and a list of candidate words — report every candidate that can actually be traced out by stepping to horizontally or vertically adjacent cells, never reusing a cell inside the same word.

THE BRIEFING — THE FULL DRILL, IN MY OWN WORDS

A letter grid and a list of candidate words arrive together. For each candidate, the question is whether it can be traced by stepping to horizontally or vertically adjacent cells, one letter per step, never reusing a cell within that word's own path.

The output is the list of candidates that actually succeed — every word that can't be traced on the board is simply left out, and a word that appears twice in the candidate list should still only be reported once.

Different candidate words may reuse the very same cells on the board; the no-reuse rule only applies within a single word's own path, not across separate words.

EX 01
board = ["race", "oats", "ring", "edym"] · words = ["race", "oats", "ring", "rore", "cast", "zzz"]
["race", "oats", "ring", "rore"]
STRAIGHT HORIZONTAL AND VERTICAL PATHS ON A 4X4 GRID, TWO CANDIDATES ABSENT
EX 02
board = ["x"] · words = ["x", "y"]
["x"]
SINGLE-CELL BOARD
EX 03
board = ["ab", "cd"] · words = ["abdc", "abc"]
["abdc"]
A WORD USING EVERY CELL EXACTLY ONCE VIA A TURN, VERSUS ONE THAT CAN'T BE FORMED AT ALL
THE HINTS — TAKE ONLY WHAT YOU NEED
HINT 1 THE NUDGE

Running the single-word board search once per candidate re-walks the same grid from scratch every time — most of that work retraces paths that no candidate even starts with. What if every candidate's search could share its early steps?

HINT 2 THE STRUCTURE

Words with the same prefix should share the same walk down the board until they actually diverge — that's exactly the shape a trie stores, letter by letter.

HINT 3 ONE STEP FROM THE ANSWER

Merge all the candidate words into one trie first, then do a single backtracking scan of the board following trie edges instead of blind neighbors. Whenever the node you land on is marked as a complete word, record it and clear that mark so it's never recorded twice.

COACH'S BOARD — THE PATTERN, STEP BY STEP
THE MERGED WALKPATTERN · TRIE-GUIDED BACKTRACKINGboard = ["an","xt"] · words = [an, ant, at]
a
n
x
t
STEP 1

Words an, ant, at merge into one trie: a branches to n (word 'an', continuing to t for 'ant') and to t (word 'at').

STEP 1 / 8 · ← → WORK TOO
THE SPLITS — TWO PACES, TWO LANGUAGES
grind/word-search-ii.pyRACE PACE
LANG ▸
PACE ▸
class TrieNode:
    def __init__(self):
        self.children = {}
        self.word = None  # the full word, set only at a terminal node


class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        root = TrieNode()
        for w in words:
            node = root
            for ch in w:
                node = node.children.setdefault(ch, TrieNode())
            node.word = w

        rows, cols = len(board), len(board[0])
        result = []

        def dfs(r: int, c: int, node: TrieNode) -> None:
            ch = board[r][c]
            child = node.children.get(ch)
            if child is None:
                return
            if child.word is not None:
                result.append(child.word)
                child.word = None  # never report the same word twice

            board[r][c] = "#"
            for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                nr, nc = r + dr, c + dc
                if 0 <= nr < rows and 0 <= nc < cols and board[nr][nc] != "#":
                    dfs(nr, nc, child)
            board[r][c] = ch

            if not child.children:
                del node.children[ch]  # dead branch, prune it for later scans

        for r in range(rows):
            for c in range(cols):
                dfs(r, c, root)

        return result
TIME O(M·N·4^L)SPACE O(Σ|WORDS|)PYTHON · RACE PACE · 42 LN

✓ CHIP-TIMED — ALL 4 SOLUTIONS RAN GREEN AGAINST SELF-AUTHORED CASES IN CI · JDK 21 · CPYTHON 3.12 · NOTHING PUBLISHES RED