[Medium] 208. Implement Trie (Prefix Tree)
A trie (pronounced as “try”) or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.
Implement the Trie class:
Trie()Initializes the trie object.void insert(String word)Inserts the stringwordinto the trie.boolean search(String word)Returnstrueif the stringwordis in the trie (i.e., was inserted before), andfalseotherwise.boolean startsWith(String prefix)Returnstrueif there is a previously inserted stringwordthat has the prefixprefix, andfalseotherwise.
Examples
Example 1:
Input
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
Output
[null, null, true, false, true, null, true]
Explanation
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // return True
trie.search("app"); // return False
trie.startsWith("app"); // return True
trie.insert("app");
trie.search("app"); // return True
Constraints
1 <= word.length, prefix.length <= 2000wordandprefixconsist only of lowercase English letters.- At most
3 * 10^4calls in total will be made toinsert,search, andstartsWith.
Thinking Process
- Self-Referential Design: Each
Trieobject is itself a node, making the structure simpler
- Strings often need frequency maps or two-pointer scans.
- Watch index bounds and empty-string edge cases.
- Stack helps with nested or repeated patterns.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Two pointers on string (this problem) | O(n) | O(1) | Palindrome, parsing |
| Hash map / frequency | O(n) | O(k) | Anagram, character counts |
| KMP / rolling hash | O(n) | O(n) | Pattern matching |
| Stack parsing | O(n) | O(n) | Decode string, parentheses |
Solution
class Trie {
public:
Trie() : children(26), isWord(false) {
}
void insert(string word) {
Trie* node = this;
for(char ch: word) {
ch -= 'a';
if(!node->children[ch]) {
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isWord = true;
}
bool search(string word) {
Trie* node = this->searchPrefix(word);
return node != nullptr && node->isWord;
}
bool startsWith(string prefix) {
return this->searchPrefix(prefix) != nullptr;
}
private:
vector<Trie*> children;
bool isWord;
Trie* searchPrefix(string prefix) {
Trie* node = this;
for(char ch: prefix) {
ch -= 'a';
if(!node->children[ch]) {
return nullptr;
}
node = node->children[ch];
}
return node;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
Solution Explanation
Approach: Two pointers on string (this problem)
Key idea: 1. Self-Referential Design: Each Trie object is itself a node, making the structure simpler
How the code works:
- Self-Referential Design: Each
Trieobject is itself a node, making the structure simpler- Strings often need frequency maps or two-pointer scans.
- Watch index bounds and empty-string edge cases.
- Stack helps with nested or repeated patterns.
Common Mistakes
- Empty string: Should be handled (mark root as end if needed)
- Single character: Works normally
- Duplicate insert: Same word inserted twice (safe, just re-marks end)
- Prefix of existing word:
insert("apple"), theninsert("app")works - Word extends prefix:
insert("app"), theninsert("apple")works - Non-existent search: Returns
falsecorrectly -
Non-existent prefix:
startsWithreturnsfalsecorrectly - Forgetting
isWordcheck:searchreturns true for prefixes if not checkingisWord - Not checking
isWordin search:search("app")returns true when only “apple” exists - Character indexing error: Forgetting
ch -= 'a'before accessingchildren[ch] - Index out of bounds: Not validating character is lowercase (0-25 range)
- Null pointer access: Not checking
if(!node->children[ch])before accessing - Incorrect traversal: Not updating
node = node->children[ch]in loop - Case sensitivity: Assuming only lowercase (problem constraint)
- Using
thisincorrectly: Forgetting that root isthisitself, not a separate pointer
When to Use Trie
- Autocomplete: Fast prefix matching
- Spell Checker: Dictionary lookup
- IP Routing: Longest prefix matching
- Search Engines: Prefix-based search
- Phone Directory: Name/contact lookup
- Word Games: Valid word checking
Related Problems
- LC 211: Design Add and Search Words Data Structure - Trie with wildcard search
- LC 212: Word Search II - Trie + DFS on board
- LC 642: Design Search Autocomplete System - Trie with frequency tracking
- LC 648: Replace Words - Trie for prefix replacement
- LC 677: Map Sum Pairs - Trie with value storage
- LC 720: Longest Word in Dictionary - Trie traversal
This problem is a fundamental data structure implementation that demonstrates the Trie (Prefix Tree) structure. It’s essential for understanding prefix-based string operations and is widely used in autocomplete systems and search engines.
Key Takeaways
- Self-Referential Design: Each
Trieobject is itself a node, making the structure simpler - Trie Structure: Tree where each path represents a prefix/word
- End Marker (
isWord): Critical to distinguish between prefix and complete word - Shared Prefixes: Multiple words sharing prefixes share nodes (space efficient)
- Array vs Map:
vector<Trie*>(26 elements) is faster and uses less memory thanunordered_map - Character Indexing:
ch -= 'a'converts character to 0-25 index efficiently - Helper Method:
searchPrefixreduces code duplication betweensearchandstartsWith - Root as
this: The Trie object itself serves as the root, eliminating need for separate root pointer
References
- LC 208: Implement Trie (Prefix Tree) on LeetCode
- LeetCode Discuss — LC 208: Implement Trie (Prefix Tree)
- LeetCode Editorial (may require premium)