Trie vs Hash Map for Prefix Matching in DSA Rust - Complexity Comparison
We want to understand how fast prefix matching works using two common data structures: Trie and Hash Map.
Which one handles searching prefixes more efficiently as the data grows?
Analyze the time complexity of the following Rust code snippets for prefix matching.
// Trie prefix search
fn trie_search(root: &TrieNode, prefix: &str) -> bool {
let mut node = root;
for ch in prefix.chars() {
if let Some(next) = node.children.get(&ch) {
node = next;
} else {
return false;
}
}
true
}
// Hash Map prefix search
fn hashmap_search(words: &Vec, prefix: &str) -> bool {
for word in words {
if word.starts_with(prefix) {
return true;
}
}
false
}
The first function searches a prefix in a Trie by following nodes. The second checks each word in a list using a Hash Map-like vector.
Look at what repeats in each method.
- Trie search primary operation: Loop over each character in the prefix.
- Trie search how many times: Once per character in prefix (length m).
- Hash Map search primary operation: Loop over all words, then check prefix match.
- Hash Map search how many times: Up to n words, each prefix check up to m characters.
How does work increase when input grows?
| Input Size (n = words, m = prefix length) | Approx. Operations |
|---|---|
| n=10, m=3 | Trie: ~3 steps; Hash Map: up to 10*3=30 steps |
| n=100, m=5 | Trie: ~5 steps; Hash Map: up to 100*5=500 steps |
| n=1000, m=10 | Trie: ~10 steps; Hash Map: up to 1000*10=10,000 steps |
Pattern observation: Trie work grows only with prefix length, while Hash Map work grows with number of words times prefix length.
Time Complexity: O(m) for Trie, O(n * m) for Hash Map
Trie searches only the prefix characters once, while Hash Map checks every word, making Trie faster for large data.
[X] Wrong: "Hash Map prefix search is always faster because hash lookups are quick."
[OK] Correct: Hash Map here means checking all words, which takes longer as words grow. Trie directly follows prefix characters, so it scales better.
Understanding these differences helps you choose the right tool for prefix search tasks, showing your grasp of efficient data handling.
What if we stored all prefixes in a Hash Map instead of full words? How would that affect the time complexity?
Practice
Solution
Step 1: Understand prefix matching requirements
Prefix matching needs to find all strings starting with a given prefix efficiently.Step 2: Compare Trie and Hash Map for prefix queries
Trie stores common prefixes once and allows traversal by characters, making prefix queries fast. Hash Map is good for exact key lookup but not for prefix searches.Final Answer:
Trie, because it stores common prefixes once and allows fast prefix queries -> Option DQuick Check:
Prefix matching = Trie [OK]
- Thinking Hash Map supports prefix queries efficiently
- Confusing exact lookup with prefix search
- Assuming arrays or lists are good for prefix matching
Solution
Step 1: Check correct HashMap declaration syntax in Rust
Rust requires specifying types inside angle brackets and calling HashMap::new() without parameters.Step 2: Validate each option
let map: std::collections::HashMap = std::collections::HashMap::new(); correctly declares a HashMap with String keys and i32 values using full path and new().Final Answer:
let map: std::collections::HashMap = std::collections::HashMap::new(); -> Option CQuick Check:
Correct Rust HashMap syntax = let map: std::collections::HashMap = std::collections::HashMap::new(); [OK]
- Using parentheses instead of ::new()
- Swapping key and value types
- Omitting type annotations when needed
struct TrieNode {
children: std::collections::HashMap,
is_end: bool,
}
impl TrieNode {
fn new() -> Self {
TrieNode { children: std::collections::HashMap::new(), is_end: false }
}
}
fn main() {
let mut root = TrieNode::new();
// Insert "cat"
let mut node = &mut root;
for c in "cat".chars() {
node = node.children.entry(c).or_insert(TrieNode::new());
}
node.is_end = true;
// Check prefix "ca"
let mut node = &root;
let mut found = true;
for c in "ca".chars() {
if let Some(next) = node.children.get(&c) {
node = next;
} else {
found = false;
break;
}
}
println!("{}", found);
}Solution
Step 1: Analyze TrieNode struct definition
TrieNode { children: HashMap, ... } creates a recursive type without indirection (e.g., Box<TrieNode>). Rust cannot compute the size of TrieNode, resulting in a compilation error.Step 2: Confirm error prevents execution
Code fails to compile, so no output or runtime behavior occurs.Final Answer:
Compilation error -> Option BQuick Check:
Recursive TrieNode without Box = compile error [OK]
- Assuming prefix must be a complete word
- Confusing is_end with prefix existence
- Ignoring Rust recursive type size requirements
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("apple", 1);
map.insert("app", 2);
let prefix = "ap";
let mut found = false;
for key in map.keys() {
if key.starts_with(prefix) {
found = true;
break;
}
}
println!("{}", found);
}Solution
Step 1: Analyze prefix matching approach
The code scans all keys linearly and checks if they start with the prefix, which works but is inefficient for large data.Step 2: Identify the bug type
There is no syntax or runtime error, but the approach is inefficient compared to Trie which supports fast prefix queries.Final Answer:
Inefficient prefix search due to linear scan over keys -> Option AQuick Check:
Linear scan for prefix = inefficient [OK]
- Thinking code has syntax or runtime errors
- Assuming HashMap keys are ordered
- Ignoring performance implications
// Option A: Use a Trie with HashMap children // Option B: Use a HashMap storing all words as keys // Option C: Use a sorted Vec and binary search for prefix // Option D: Use a LinkedList of words and scan linearly
Solution
Step 1: Evaluate insertion and prefix search efficiency
Trie allows insertion and prefix search in O(k) time, where k is prefix length, by sharing common prefixes.Step 2: Compare other data structures
HashMap is fast for exact keys but slow for prefix search. Sorted Vec requires complex prefix range search. LinkedList is slow for prefix search.Final Answer:
Trie, because it shares prefixes and allows O(k) prefix search where k is prefix length -> Option AQuick Check:
Fast prefix search = Trie [OK]
- Choosing HashMap for prefix search
- Ignoring complexity of prefix range search in sorted Vec
- Underestimating LinkedList search cost
Ready to prove it?
Pass the test and this pattern moves unknown
