Trie-Based String Problems
Use tries for autocomplete, word search, longest common prefix, and wildcard matching, with efficient prefix lookups and character-by-character traversal.
Terminology
| Term | Definition |
|---|---|
| Trie (prefix tree) | A tree data structure where each node represents a character. Paths from root to nodes spell out prefixes. Enables $O(L)$ lookup where $L$ is the word length. |
| Trie node | Contains a map of child characters and a boolean flag indicating whether this node marks the end of a complete word. |
| Prefix search | Checking whether any word in the trie starts with a given prefix. Traverse the trie character by character; if you reach the end of the prefix without a dead end, the prefix exists. |
| Autocomplete | Given a prefix, find all words that start with it. Navigate to the prefix node, then DFS to collect all words in the subtree. |
| Longest common prefix | The longest string that is a prefix of all words in a set. In a trie, follow the path where each node has exactly one child until a branch or word-end is reached. |
| Wildcard matching | Searching for words where some characters are wildcards (e.g., "." matches any character). Requires branching into all children at wildcard positions. |
| Word search (grid) | Finding words from a dictionary in a 2D character grid. A trie prunes the search: if no word starts with the current path, stop exploring. |
What & Why
A trie stores a set of strings so that any prefix lookup takes O(L) time, where L is the length of the query, regardless of how many strings are stored. This makes it ideal for autocomplete, spell checking, and prefix-based filtering.
Compared to a hash set (which gives O(L) exact lookup but no prefix support) or a sorted array (which gives O(L \log n) prefix search via binary search), a trie provides O(L) for both exact and prefix queries, plus the ability to enumerate all words sharing a prefix.
In LeetCode, tries appear in "implement trie," "word search II" (finding dictionary words in a grid), "longest common prefix," and "add and search word" (wildcard matching). The trie acts as a shared prefix tree that avoids redundant character comparisons.
How It Works
Trie Structure
Each node has up to 26 children (for lowercase English letters) and an isEnd flag. Inserting "cat" and "car" shares the "ca" prefix.
Autocomplete
Navigate to the prefix node, then run DFS from that node to collect all complete words in the subtree. This gives all words starting with the prefix.
Wildcard Matching
When the query character is a wildcard (e.g., "."), branch into all existing children at that position. This turns a single traversal into a bounded DFS.
Complexity Analysis
| Operation | Time | Space |
|---|---|---|
| Insert word | $O(L)$ | $O(L)$ new nodes worst case |
| Search exact word | $O(L)$ | $O(1)$ |
| Prefix search | $O(L)$ | $O(1)$ |
| Autocomplete (all matches) | $O(L + M)$ where $M$ = total chars in results | $O(M)$ |
| Wildcard search | $O(26^w \cdot L)$ where $w$ = wildcard count | $O(L)$ recursion depth |
Total space for a trie storing $n$ words of average length $L$ with alphabet size $|\Sigma|$:
Implementation
Basic Trie: Insert, Search, StartsWith
ALGORITHM TrieNode()
children = map of character → TrieNode (initially empty)
isEnd = FALSE
END
ALGORITHM Insert(root, word)
node = root
FOR EACH char IN word DO
IF char NOT IN node.children THEN
node.children[char] = new TrieNode()
END IF
node = node.children[char]
END FOR
node.isEnd = TRUE
END
ALGORITHM Search(root, word)
node = root
FOR EACH char IN word DO
IF char NOT IN node.children THEN
RETURN FALSE
END IF
node = node.children[char]
END FOR
RETURN node.isEnd
END
ALGORITHM StartsWith(root, prefix)
node = root
FOR EACH char IN prefix DO
IF char NOT IN node.children THEN
RETURN FALSE
END IF
node = node.children[char]
END FOR
RETURN TRUE
END
Autocomplete
ALGORITHM Autocomplete(root, prefix)
INPUT: trie root, prefix string
OUTPUT: list of all words starting with prefix
node = root
FOR EACH char IN prefix DO
IF char NOT IN node.children THEN
RETURN empty list
END IF
node = node.children[char]
END FOR
results = empty list
DFS(node, prefix, results)
RETURN results
END
ALGORITHM DFS(node, currentWord, results)
IF node.isEnd THEN
results.append(currentWord)
END IF
FOR EACH (char, child) IN node.children DO
DFS(child, currentWord + char, results)
END FOR
END
Wildcard Search (Add and Search Word)
ALGORITHM WildcardSearch(node, word, index)
IF index = LENGTH(word) THEN
RETURN node.isEnd
END IF
char = word[index]
IF char = '.' THEN
FOR EACH (c, child) IN node.children DO
IF WildcardSearch(child, word, index + 1) THEN
RETURN TRUE
END IF
END FOR
RETURN FALSE
ELSE
IF char NOT IN node.children THEN
RETURN FALSE
END IF
RETURN WildcardSearch(node.children[char], word, index + 1)
END IF
END
Real-World Applications
- Autocomplete and search suggestions: Google, IDE code completion, and phone keyboards use tries (or compressed variants like ternary search trees) for instant prefix matching.
- Spell checkers: dictionaries stored as tries enable fast lookup and "did you mean?" suggestions by exploring nearby branches.
- IP routing tables: longest prefix matching in network routers uses a bitwise trie to find the most specific route for an IP address.
- Word games: Scrabble solvers and Boggle solvers use tries to prune invalid paths during board traversal.
- Genome analysis: suffix tries and suffix trees index DNA sequences for fast substring and pattern matching.
Key Takeaways
- Tries provide $O(L)$ insert, search, and prefix lookup regardless of how many words are stored, where $L$ is the query length
- Shared prefixes save space: "cat," "car," "card," and "cap" share the "ca" path, storing only 7 nodes instead of 14 characters
- Autocomplete navigates to the prefix node and DFS-collects all words in the subtree
- Wildcard matching branches into all children at wildcard positions, bounded by the alphabet size per wildcard
- For "word search II" on grids, build a trie from the dictionary and DFS the grid, pruning paths that do not match any trie prefix