Autocomplete System with Trie in DSA Rust - 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.
struct TrieNode {
children: [Option; 26],
is_word: bool,
}
impl TrieNode {
fn search_prefix(&self, prefix: &str) -> Option<&TrieNode> {
let mut node = self;
for ch in prefix.chars() {
let idx = (ch as usize) - ('a' as usize);
match &node.children[idx] {
Some(child) => node = child,
None => return None,
}
}
Some(node)
}
}
This code searches the trie for the node matching the typed prefix.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping over each character in the prefix string.
- How many times: Once for each letter typed (length of prefix).
As you type more letters, the search checks one more child node each time.
| Input Size (prefix length) | Approx. Operations |
|---|---|
| 10 | 10 child lookups |
| 100 | 100 child lookups |
| 1000 | 1000 child lookups |
Pattern observation: The work grows directly with how many letters you type.
Time Complexity: O(m)
This means the search time grows linearly with the length of the prefix typed.
[X] Wrong: "The search time depends on the total number of words stored in the trie."
[OK] Correct: The search only follows the prefix path, so it depends on prefix length, not total words.
Understanding this helps you explain how autocomplete can be fast even with many words stored.
"What if we changed the trie to store words with uppercase letters too? How would the time complexity change?"
Practice
Trie in an autocomplete system?Solution
Step 1: Understand the role of Trie in prefix search
A Trie is a tree-like data structure designed to store strings by their prefixes, allowing quick prefix-based searches.Step 2: Connect Trie usage to autocomplete
Autocomplete requires finding all words starting with a given prefix efficiently, which a Trie supports well.Final Answer:
To efficiently store and search prefixes of words -> Option CQuick Check:
Trie = prefix search [OK]
- Confusing Trie with sorting data structure
- Thinking Trie counts words only
- Assuming Trie reverses strings
Solution
Step 1: Understand Rust ownership and recursive types
Rust requires recursive types like TrieNode to use pointers such as Box to avoid infinite size errors.Step 2: Check each option's children type
struct TrieNode { children: HashMap, is_end: bool } uses HashMap, which is correct for recursive Trie nodes. struct TrieNode { children: HashMap, is_end: bool } lacks Box, causing compile error. struct TrieNode { children: Vec<char>, is_end: bool } uses Vec<char>, which is incorrect for children mapping. struct TrieNode { children: HashMap, is_end: bool } uses HashMap, which is incorrect because keys should be single chars.Final Answer:
struct TrieNode { children: HashMap, is_end: bool } -> Option AQuick Check:
Recursive struct needs Box pointer [OK]
- Omitting Box causes compile errors
- Using Vec instead of HashMap for children
- Using String keys instead of char
"ca"?
let mut trie = Trie::new();
trie.insert("cat");
trie.insert("car");
trie.insert("dog");
let results = trie.search_prefix("ca");
println!("{:?}", results);Solution
Step 1: Insert words into Trie
The words "cat", "car", and "dog" are inserted. "cat" and "car" share prefix "ca".Step 2: Search for prefix "ca"
Searching prefix "ca" returns all words starting with "ca", which are "cat" and "car".Final Answer:
["cat", "car"] -> Option BQuick Check:
Prefix "ca" matches "cat", "car" [OK]
- Returning only one matching word
- Returning words without the prefix
- Returning empty list incorrectly
fn insert(&mut self, word: &str) {
let mut node = &self.root;
for ch in word.chars() {
node = node.children.entry(ch).or_insert(TrieNode::new());
}
node.is_end = true;
}Solution
Step 1: Analyze mutability of node reference
node is assigned as immutable reference &self.root, but children need to be modified, so node must be mutable reference &mut.Step 2: Understand why mutability is needed for entry()
entry() modifies the HashMap, so node must be mutable to call entry() and insert new nodes.Final Answer:
node should be mutable reference (&mut) to modify children -> Option DQuick Check:
Mutable reference needed for insertion [OK]
- Using immutable reference for mutable operations
- Setting is_end too early
- Unnecessary collection of chars
Solution
Step 1: Understand frequency storage in Trie nodes
Storing frequency counts at end nodes tracks how often each word is used.Step 2: Use max-heap to retrieve top frequent words efficiently
During prefix search, a max-heap can quickly select highest frequency words without sorting all results.Final Answer:
Store a frequency count at each end node and use a max-heap during search -> Option AQuick Check:
Frequency + max-heap = efficient top suggestions [OK]
- Sorting all words on every search (slow)
- Ignoring frequency data
- Using linked list which is inefficient for sorting
Ready to prove it?
Pass the test and this pattern moves unknown
