Trie vs Hash Map for Prefix Matching in DSA Go - Complexity Comparison
We want to understand how fast prefix matching works using two common data structures: Trie and Hash Map.
Which one handles prefix searches more efficiently as the input grows?
Analyze the time complexity of the following Trie prefix search code snippet.
// Search prefix in Trie
func (t *Trie) StartsWith(prefix string) bool {
node := t.root
for _, ch := range prefix {
if node.children[ch] == nil {
return false
}
node = node.children[ch]
}
return true
}
This code checks if any word in the Trie starts with the given prefix.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop over each character in the prefix string.
- How many times: Exactly once per character in the prefix (length m).
As the prefix length grows, the number of steps grows linearly.
| Input Size (prefix length m) | Approx. Operations |
|---|---|
| 10 | 10 steps |
| 100 | 100 steps |
| 1000 | 1000 steps |
Pattern observation: The time grows directly with prefix length, not with total words stored.
Time Complexity: O(m)
This means the search time depends only on the prefix length, making it very efficient for prefix queries.
[X] Wrong: "Trie search time depends on the total number of words stored."
[OK] Correct: Trie search only follows the prefix characters, so it depends on prefix length, not total words.
Understanding how Trie and Hash Map handle prefix matching helps you choose the right tool for fast lookups in real applications.
"What if we used a Hash Map storing all prefixes as keys? How would the time complexity for prefix search change?"
Practice
Solution
Step 1: Understand prefix search requirements
Prefix search needs to quickly find if any string starts with a given prefix.Step 2: Compare Trie and Hash Map for prefix search
Trie stores prefixes explicitly, allowing fast traversal to check prefixes. Hash Map is good for exact key lookup but not for prefix matching.Final Answer:
Trie, because it stores prefixes explicitly and allows fast prefix search -> Option DQuick Check:
Prefix search = Trie [OK]
- Thinking hash map supports prefix search efficiently
- Confusing prefix search with exact key lookup
- Assuming arrays or lists are efficient for prefix matching
Solution
Step 1: Identify correct field types for TrieNode
Children should map characters (rune) to TrieNode pointers; isEnd is a boolean indicating word end.Step 2: Check each option's correctness
type TrieNode struct { children map[rune]*TrieNode; isEnd bool } uses map[rune]*TrieNode and bool for isEnd, which is correct. Others have wrong types or field names.Final Answer:
type TrieNode struct { children map[rune]*TrieNode; isEnd bool } -> Option BQuick Check:
TrieNode struct with map[rune]*TrieNode and bool isEnd = type TrieNode struct { children map[rune]*TrieNode; isEnd bool } [OK]
- Using incorrect types for children map keys or values
- Using string instead of bool for isEnd
- Confusing field names or types
type TrieNode struct {
children map[rune]*TrieNode
isEnd bool
}
func (t *TrieNode) Insert(word string) {
node := t
for _, ch := range word {
if node.children[ch] == nil {
node.children[ch] = &TrieNode{children: make(map[rune]*TrieNode)}
}
node = node.children[ch]
}
node.isEnd = true
}
func (t *TrieNode) StartsWith(prefix string) bool {
node := t
for _, ch := range prefix {
if node.children[ch] == nil {
return false
}
node = node.children[ch]
}
return true
}
func main() {
root := &TrieNode{children: make(map[rune]*TrieNode)}
root.Insert("cat")
root.Insert("car")
root.Insert("dog")
fmt.Println(root.StartsWith("ca"))
fmt.Println(root.StartsWith("do"))
fmt.Println(root.StartsWith("bat"))
}Solution
Step 1: Insert words into Trie
Words "cat", "car", and "dog" are inserted, building paths for 'c'->'a'->'t', 'c'->'a'->'r', and 'd'->'o'->'g'.Step 2: Check StartsWith calls
StartsWith("ca") returns true because 'c'->'a' path exists. StartsWith("do") returns true because 'd'->'o' path exists. StartsWith("bat") returns false because 'b' path does not exist.Final Answer:
true true false -> Option AQuick Check:
Prefix "ca" and "do" found, "bat" not found = true true false [OK]
- Assuming StartsWith returns true only if full word exists
- Confusing Insert with StartsWith behavior
- Ignoring that 'bat' prefix is missing
func PrefixExists(words map[string]bool, prefix string) bool {
for word := range words {
if strings.HasPrefix(word, prefix) {
return true
}
}
return false
}
func main() {
words := map[string]bool{"apple": true, "app": true, "banana": true}
fmt.Println(PrefixExists(words, "ap"))
}Solution
Step 1: Analyze PrefixExists function logic
The function iterates over all keys in the map and checks prefix using strings.HasPrefix.Step 2: Identify performance issue
Iterating all keys causes linear time complexity, which is inefficient for large datasets compared to Trie prefix search.Final Answer:
Inefficient for large datasets due to linear scan over all keys -> Option AQuick Check:
Linear scan in map for prefix = inefficient [OK]
- Thinking strings.HasPrefix is invalid
- Assuming map iteration is not allowed
- Confusing return types
Solution
Step 1: Evaluate each data structure for prefix search on large data
Hash Map requires scanning all keys for prefix, inefficient. Trie is efficient and allows prefix traversal without scanning all words. Sorted array allows binary search to find prefix range quickly but requires additional steps to find all matches.Step 2: Compare Trie and sorted array for large datasets
Trie uses more memory but provides direct prefix traversal and efficient prefix queries. Sorted array with binary search is efficient for prefix range queries but retrieving all matches requires scanning the range.Final Answer:
Trie allows efficient prefix traversal without scanning all words -> Option CQuick Check:
Trie prefix traversal = efficient prefix search [OK]
- Choosing sorted array without considering retrieval cost
- Assuming hash map supports fast prefix search
- Ignoring Trie advantages for prefix queries
Ready to prove it?
Pass the test and this pattern moves unknown
