DSA Trie

Word Search II

Find every dictionary word in a board — trie-pruned DFS over the grid.

Hard
Time O(M * N * 4^L) Space O(W * L)
LeetCode
8 min read
trie dfs backtracking grid

Problem#

Given an m x n board of letters and a dictionary of words, return all dictionary words present on the board. A word can be built from sequentially adjacent cells (up/down/left/right) and each cell may not be reused within a single word.

Examples#

  • board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]["oath","eat"].

Constraints#

  • 1 <= m, n <= 12, 1 <= words.length <= 3 * 10^4, 1 <= words[i].length <= 10.

Hints#

Hint 1
Searching each word independently is too slow — share traversal via a trie of all words.
Hint 2
Prune subtrees of the trie as soon as a word is matched, so duplicates aren’t re-discovered.

Approach#

Per-word DFS — O(W * M * N * 4^L). Too slow when W is large.
Trie + grid DFS — build trie of all words, walk grid once; each path follows trie children only.

DFS from every cell, descending in the trie. When you hit a trie node with a word field set, collect it and null the field to dedupe. Prune leaf trie nodes after collection to shrink future work.

Solution#

Word Search II
class Solution {
struct Node {
Node* child[26]{};
string word; // non-empty iff this node is a word end
};
void insert(Node* root, const string& w) {
Node* cur = root;
for (char c : w) {
int i = c - 'a';
if (!cur->child[i]) cur->child[i] = new Node();
cur = cur->child[i];
}
cur->word = w;
}
void dfs(vector<vector<char>>& b, int r, int c, Node* node, vector<string>& out) {
char ch = b[r][c];
if (ch == '#' || !node->child[ch - 'a']) return;
Node* next = node->child[ch - 'a'];
if (!next->word.empty()) { out.push_back(next->word); next->word.clear(); }
b[r][c] = '#';
if (r + 1 < (int)b.size()) dfs(b, r + 1, c, next, out);
if (r - 1 >= 0) dfs(b, r - 1, c, next, out);
if (c + 1 < (int)b[0].size()) dfs(b, r, c + 1, next, out);
if (c - 1 >= 0) dfs(b, r, c - 1, next, out);
b[r][c] = ch;
// Optional pruning: detach leaf trie nodes.
bool empty = true;
for (int i = 0; i < 26; ++i) if (next->child[i]) { empty = false; break; }
if (empty && next->word.empty()) node->child[ch - 'a'] = nullptr;
}
public:
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
Node* root = new Node();
for (auto& w : words) insert(root, w);
vector<string> ans;
for (int r = 0; r < (int)board.size(); ++r)
for (int c = 0; c < (int)board[0].size(); ++c)
dfs(board, r, c, root, ans);
return ans;
}
};

Editorial#

Sharing prefixes across the dictionary is the win — a single grid DFS now matches as many words as fit each path’s prefix. Clearing node->word after a hit prevents duplicates; pruning empty leaf nodes shrinks the trie as words are consumed, making later cells faster.

Complexity#

  • Time: O(M * N * 4^L) worst case (L = max word length).
  • Space: O(W * L) for the trie.

Concept revision#

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.