Autocomplete System with Trie in DSA Go - Time & Space Complexity
We want to understand how fast an autocomplete system using a trie can find suggestions.
How does the time to find words grow as we add more words or type more letters?
Analyze the time complexity of the following code snippet.
// Search for words starting with prefix in trie
func (t *Trie) SearchPrefix(prefix string) []*TrieNode {
node := t.root
for _, ch := range prefix {
if node.children[ch-'a'] == nil {
return nil
}
node = node.children[ch-'a']
}
return collectWords(node)
}
func collectWords(node *TrieNode) []*TrieNode {
// Collect all words under this node
// ... (recursive traversal)
return nil // placeholder to avoid syntax error
}
This code finds the node matching the prefix, then collects all words below it.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop over prefix characters and recursive traversal of trie nodes.
- How many times: Loop runs once per prefix letter; recursion visits all descendant nodes for suggestions.
Typing more letters means the loop runs more times, but fewer words match, so recursion visits fewer nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| Prefix length 3, 1000 words | 3 + visits to matching subtree nodes |
| Prefix length 5, 1000 words | 5 + fewer subtree nodes visited |
| Prefix length 5, 10,000 words | 5 + more subtree nodes visited |
Pattern observation: More prefix letters mean more initial steps but fewer words to check; more words stored means bigger subtree to explore.
Time Complexity: O(p + m)
This means the time grows with the prefix length p plus the number of matched words m found below.
[X] Wrong: "The search time depends only on the number of words stored."
[OK] Correct: The search depends mainly on prefix length and matched words, not total words stored.
Understanding this helps you explain how tries speed up prefix searches, a common real-world feature like search suggestions.
"What if we changed the trie to store counts at each node to avoid recursion? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of Trie in prefix search
A Trie is a tree-like data structure that stores characters of words in a way that common prefixes share nodes.Step 2: Connect Trie usage to autocomplete
Autocomplete requires quick lookup of words starting with a given prefix, which Trie supports efficiently.Final Answer:
To efficiently store and search prefixes of words -> Option CQuick Check:
Trie = prefix search [OK]
- Thinking Trie sorts words alphabetically
- Confusing Trie with encryption methods
- Assuming Trie counts total words only
Solution
Step 1: Identify correct data types for TrieNode fields
Children should map characters (rune) to child nodes (pointer to TrieNode). isEnd should be a boolean indicating word end.Step 2: Match with given options
type TrieNode struct { children map[rune]*TrieNode; isEnd bool } uses map[rune]*TrieNode and bool for isEnd, which is correct for Trie implementation in Go.Final Answer:
type TrieNode struct { children map[rune]*TrieNode; isEnd bool } -> Option BQuick Check:
Children map[rune]*TrieNode and bool isEnd [OK]
- Using slices instead of maps for children
- Using incorrect types like int or string for isEnd
- Mapping children with wrong key or value types
trie := NewTrie()
trie.Insert("apple")
trie.Insert("app")
trie.Insert("application")
results := trie.SearchPrefix("app")
fmt.Println(results)
Assuming SearchPrefix returns all words starting with the prefix.Solution
Step 1: Insert words into Trie
Words "apple", "app", and "application" are inserted, all sharing prefix "app".Step 2: Search for prefix "app"
SearchPrefix returns all words starting with "app", so it returns all three inserted words.Final Answer:
["app", "apple", "application"] -> Option DQuick Check:
All words with prefix "app" returned [OK]
- Returning only exact prefix word
- Missing some words sharing prefix
- Returning empty list incorrectly
func (t *Trie) Insert(word string) {
node := t.root
for _, ch := range word {
if node.children[ch] == nil {
node.children[ch] = &TrieNode{}
}
node = node.children[ch]
}
node.isEnd = false
}Solution
Step 1: Review isEnd assignment after insertion
After inserting a word, isEnd should mark the node as end of a word, so it must be set to true.Step 2: Check other parts of code
children map initialization and loop variable type are correct; function does not need to return a value.Final Answer:
isEnd should be set to true after insertion -> Option AQuick Check:
isEnd = true after insert [OK]
- Setting isEnd to false instead of true
- Not initializing children map before use
- Expecting Insert to return a value
Solution
Step 1: Add frequency tracking in Trie nodes
Modify Trie nodes to store frequency count at nodes marking word ends to track usage.Step 2: Sort autocomplete results by frequency
When returning suggestions, sort collected words by their stored frequency counts to prioritize popular words.Final Answer:
Store a frequency count at each end node and sort results by this count -> Option AQuick Check:
Frequency count in Trie nodes enables sorted suggestions [OK]
- Sorting words before insertion does not help frequency
- Using unrelated arrays breaks Trie efficiency
- Ignoring frequency loses popular word priority
Ready to prove it?
Pass the test and this pattern moves unknown
