🟨 Implement Trie (Prefix Tree) (#208)
🔗 Problem Link
📋 Problem Statement
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, 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]
🔑 Key Insights & Approach
Core Observation: Each node has children for each character (a-z) and a flag for word end.
Why Trie?
- Insert: O(m) where m = word length
- Search: O(m)
- Prefix search: O(m)
- Space: O(ALPHABET_SIZE * N * M) worst case
Approaches:
- Array-based children: O(1) child lookup, more space
- HashMap-based children: O(1) avg lookup, less space
Pattern: "Trie Data Structure" for prefix operations.
🐍 Solution: Python
Approach: HashMap-based Children
Time Complexity: O(m) per operation | Space Complexity: O(N * M)
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
def search(self, word: str) -> bool:
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end
def startsWith(self, prefix: str) -> bool:
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
Approach 2: Array-based (26 letters)
Time Complexity: O(m) per operation | Space Complexity: O(26 * N * M)
class TrieNode:
def __init__(self):
self.children = [None] * 26
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def _char_to_index(self, char):
return ord(char) - ord('a')
def insert(self, word: str) -> None:
node = self.root
for char in word:
idx = self._char_to_index(char)
if not node.children[idx]:
node.children[idx] = TrieNode()
node = node.children[idx]
node.is_end = True
def search(self, word: str) -> bool:
node = self.root
for char in word:
idx = self._char_to_index(char)
if not node.children[idx]:
return False
node = node.children[idx]
return node.is_end
def startsWith(self, prefix: str) -> bool:
node = self.root
for char in prefix:
idx = self._char_to_index(char)
if not node.children[idx]:
return False
node = node.children[idx]
return True
🔵 Solution: Golang
Approach: Map-based Children
Time Complexity: O(m) per operation | Space Complexity: O(N * M)
type TrieNode struct {
children map[rune]*TrieNode
isEnd bool
}
type Trie struct {
root *TrieNode
}
func Constructor() Trie {
return Trie{
root: &TrieNode{
children: make(map[rune]*TrieNode),
isEnd: false,
},
}
}
func (this *Trie) Insert(word string) {
node := this.root
for _, char := range word {
if _, exists := node.children[char]; !exists {
node.children[char] = &TrieNode{
children: make(map[rune]*TrieNode),
isEnd: false,
}
}
node = node.children[char]
}
node.isEnd = true
}
func (this *Trie) Search(word string) bool {
node := this.root
for _, char := range word {
if _, exists := node.children[char]; !exists {
return false
}
node = node.children[char]
}
return node.isEnd
}
func (this *Trie) StartsWith(prefix string) bool {
node := this.root
for _, char := range prefix {
if _, exists := node.children[char]; !exists {
return false
}
node = node.children[char]
}
return true
}