Word Search II

HardTrie · Backtracking (Grid DFS)🧩 TrieLeetCode #212 ↗Updated

Problem

You get an m x n grid of lowercase letters and a list of distinct words. Return every word that can be spelled by a path of horizontally or vertically adjacent cells, where a single cell may be used at most once per word. The answer may be in any order.
Inputoaan/etae/ihkr/iflv, [oath,pea,eat,rain]Output[eat,oath]
Inputab/cd, [abcb]Output[]

1 ≤ m, n ≤ 12  ·  1 ≤ words.length ≤ 3·104  ·  1 ≤ word length ≤ 10  ·  words are unique, lowercase only

Try it yourself

Java
class Solution {
    public List<String> findWords(char[][] board, String[] words) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Don't search for words one at a time — put them all in a Trie and walk the grid and the Trie together. A path stops the moment its prefix is not in the Trie, and one DFS from a cell finds every word starting there.
PatternTrie

Instinct Many words, shared prefixes, and questions like "starts with", autocomplete, wildcard match or "search a board for all these words" - build a prefix tree.

Memory hack Words share their beginnings, so store each beginning once - walk one letter per step, and an end flag marks where a real word stops.

Open the Trie guide →

Approaches

Brute ForceWord Search (DFS) for Every Word

Time O(W·m·n·3L)Space O(L)

Solve Word Search I for each word: try every cell as a start and backtrack in 4 directions, marking path cells with '#'. Words sharing a prefix redo the same exploration again and again.

Java
public List<String> findWords(char[][] board, String[] words) {
    List<String> result = new ArrayList<>();
    for (String word : words) {
        search:
        for (int i = 0; i < board.length; i++)
            for (int j = 0; j < board[0].length; j++)
                if (dfs(board, word, i, j, 0)) { result.add(word); break search; }
    }
    return result;
}

private boolean dfs(char[][] b, String word, int i, int j, int idx) {
    if (idx == word.length()) return true;
    if (i < 0 || j < 0 || i >= b.length || j >= b[0].length
            || b[i][j] != word.charAt(idx)) return false;
    char tmp = b[i][j];
    b[i][j] = '#'; // mark visited
    boolean found = dfs(b, word, i + 1, j, idx + 1) || dfs(b, word, i - 1, j, idx + 1)
                 || dfs(b, word, i, j + 1, idx + 1) || dfs(b, word, i, j - 1, idx + 1);
    b[i][j] = tmp; // backtrack
    return found;
}

Simple, but with 3·104 words the repeated grid scans time out.

At a glance

LevelApproachTimeSpace
Brute ForceWord Search (DFS) for Every WordO(W·m·n·3L)O(L)
OptimalTrie + Backtracking with PruningO(m·n·3L + S)O(S)
Common pitfalls
  • Forgetting to restore board[i][j] after the DFS — later searches see '#'.
  • Not clearing node.word — the same word is reported from several paths.
  • Computing '#' - 'a' as an index — check the visited mark before the Trie lookup.
Interview tips
  • Start from Word Search I per word, then point out the shared-prefix waste.
  • Explain why the Trie turns W separate searches into one simultaneous search.
  • Mention pruning dead leaves as the extra optimisation that passes the hardest tests.
Pattern
Trie-guided backtracking → search many words/prefixes at once in a grid or string.
My notes saved in this browser