Trie vs Hash Map for Prefix Matching in DSA C++ - Complexity Comparison
We want to understand how fast we can find words that start with a given prefix using two common data structures.
Which one is faster when the input grows: a Trie or a Hash Map?
Analyze the time complexity of the following code snippet.
// Trie prefix search
bool startsWith(TrieNode* root, const string& prefix) {
TrieNode* node = root;
for (char c : prefix) {
if (!node->children.count(c)) return false;
node = node->children[c];
}
return true;
}
// Hash Map prefix search
bool startsWithHashMap(const unordered_map<string, bool>& wordsMap, const string& prefix) {
for (const auto& [word, _] : wordsMap) {
if (word.compare(0, prefix.size(), prefix) == 0) return true;
}
return false;
}
The first function checks if any word in the Trie starts with the prefix by walking down nodes.
The second function checks each word in the hash map to see if it starts with the prefix.
Identify the loops, recursion, array traversals that repeat.
- Primary operation in Trie: Loop over prefix characters.
- How many times: Once per character in prefix (length m).
- Primary operation in Hash Map: Loop over all words, then compare prefix.
- How many times: For n words, each comparison up to m characters.
Explain the growth pattern intuitively.
| Input Size (n) | Approx. Operations Trie | Approx. Operations Hash Map |
|---|---|---|
| 10 | ~m (prefix length) | 10 * m |
| 100 | ~m | 100 * m |
| 1000 | ~m | 1000 * m |
Pattern observation: Trie operations grow only with prefix length, while Hash Map operations grow with number of words times prefix length.
Time Complexity: O(m) for Trie, O(n * m) for Hash Map
This means Trie searches grow only with prefix length, while Hash Map searches grow with total words and prefix length.
[X] Wrong: "Hash Map prefix search is always faster because hash lookups are quick."
[OK] Correct: Hash Maps are fast for exact keys, but checking prefixes requires scanning many keys, which slows down as word count grows.
Knowing when to use a Trie or Hash Map for prefix matching shows you understand trade-offs in data structures and efficiency.
"What if we stored all prefixes as keys in the Hash Map? How would that change the time complexity for prefix search?"
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 stores full keys, so prefix search requires checking many keys or extra processing.Final Answer:
Trie, because it stores prefixes explicitly and allows fast prefix search -> Option DQuick Check:
Prefix search = Trie [OK]
- Thinking hash map can do prefix search in constant time
- Confusing prefix search with exact key search
- Ignoring the need to check multiple keys in hash map
Solution
Step 1: Identify correct children type for fixed alphabet
For lowercase English letters, fixed size array of 26 pointers is efficient.Step 2: Check each option's children type
struct TrieNode { TrieNode* children[26]; bool isEnd; }; uses TrieNode* children[26], which is correct. struct TrieNode { map children; bool isEnd; }; uses map, which is valid but slower. Options A and C use incorrect types.Final Answer:
struct TrieNode { TrieNode* children[26]; bool isEnd; }; -> Option AQuick Check:
Fixed alphabet Trie node = array of 26 pointers [OK]
- Using vector<int> instead of pointers for children
- Using unordered_map with wrong key type
- Confusing map with array for fixed alphabets
struct TrieNode {
TrieNode* children[26] = {nullptr};
bool isEnd = false;
};
TrieNode* root = new TrieNode();
// Insert "cat"
string word = "cat";
TrieNode* node = root;
for (char c : word) {
int index = c - 'a';
if (!node->children[index])
node->children[index] = new TrieNode();
node = node->children[index];
}
node->isEnd = true;
// Check prefix "ca"
node = root;
string prefix = "ca";
bool found = true;
for (char c : prefix) {
int index = c - 'a';
if (!node->children[index]) {
found = false;
break;
}
node = node->children[index];
}
cout << (found ? "Prefix exists" : "Prefix not found") << endl;
Solution
Step 1: Insert "cat" into Trie
Each character 'c', 'a', 't' creates nodes if missing. After insertion, path for "cat" exists.Step 2: Check prefix "ca"
Traverse nodes for 'c' and 'a'. Both nodes exist, so prefix is found.Final Answer:
Prefix exists -> Option AQuick Check:
Inserted "cat" contains prefix "ca" [OK]
- Assuming prefix must be a full word
- Forgetting to check each character node
- Confusing isEnd with prefix existence
bool startsWith(TrieNode* root, const string& prefix) {
TrieNode* node = root;
for (char c : prefix) {
int idx = c - 'a';
if (node->children[idx] == nullptr)
return false;
node = node->children[idx];
}
return node->isEnd;
}Solution
Step 1: Understand prefix search logic
Prefix search should return true if all prefix nodes exist, regardless of isEnd.Step 2: Identify incorrect return statement
Returning node->isEnd checks if prefix is a full word, not just a prefix. It should return true after loop.Final Answer:
Should return true after loop, not node->isEnd -> Option BQuick Check:
Prefix search returns true if prefix nodes exist [OK]
- Confusing prefix existence with full word existence
- Incorrect index calculation for characters
- Not checking for null root pointer
Option A: Use a sorted array of strings and binary search for prefixes.
Option B: Use a Hash Map storing all strings and check prefixes by scanning keys.
Option C: Use a Trie for prefix matching.
Option D: Use a linked list of strings and linear search for prefixes.Solution
Step 1: Analyze requirements for millions of strings with dynamic insertions
Dynamic insertions require efficient updates; prefix matching needs fast queries.Step 2: Compare data structures for prefix matching and insertions
Trie supports fast prefix search and efficient insertions. Hash Map is fast for exact keys but slow for prefix. Sorted array requires costly insertions. Linked list is slow for both.Final Answer:
Trie, because it provides efficient prefix search and dynamic insertions -> Option CQuick Check:
Dynamic prefix search = Trie [OK]
- Choosing hash map for prefix search without scanning keys
- Ignoring insertion cost in sorted arrays
- Underestimating linked list search time
Ready to prove it?
Pass the test and this pattern moves unknown
