Autocomplete System with Trie in DSA C++ - Time & Space Complexity
We want to understand how fast an autocomplete system using a trie can find suggestions based on a prefix.
How does the time to find suggestions grow as the prefix or dictionary size changes?
Analyze the time complexity of the following code snippet.
struct TrieNode {
TrieNode* children[26] = {nullptr};
bool isWord = false;
};
void insert(TrieNode* root, const std::string& word) {
TrieNode* node = root;
for (char c : word) {
int idx = c - 'a';
if (!node->children[idx])
node->children[idx] = new TrieNode();
node = node->children[idx];
}
node->isWord = true;
}
TrieNode* searchPrefix(TrieNode* root, const std::string& prefix) {
TrieNode* node = root;
for (char c : prefix) {
int idx = c - 'a';
if (!node->children[idx])
return nullptr;
node = node->children[idx];
}
return node;
}
This code inserts words into a trie and searches for the node matching a prefix.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through each character of the input word or prefix.
- How many times: Once per character, so the number of characters in the word or prefix (let's call it k).
As the prefix length k grows, the search checks one more character each time.
| Input Size (prefix length k) | Approx. Operations |
|---|---|
| 10 | About 10 character checks |
| 100 | About 100 character checks |
| 1000 | About 1000 character checks |
Pattern observation: The time grows linearly with the prefix length, not with the total number of words stored.
Time Complexity: O(k)
This means the time to find the prefix node grows only with the length of the prefix, regardless of how many words are stored.
[X] Wrong: "The search time depends on the total number of words stored in the trie."
[OK] Correct: The search only follows the prefix characters, so it depends on prefix length, not total words.
Understanding this helps you explain why tries are efficient for autocomplete, showing you know how data structure choice affects speed.
"What if we also want to list all words starting with the prefix? How would that affect the time complexity?"
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 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 by traversing prefix nodes.Final Answer:
To efficiently store and search prefixes of words -> Option AQuick Check:
Trie = prefix search [OK]
- Confusing Trie with sorting structures
- Thinking Trie stores words linearly
- Assuming Trie does arithmetic
Solution
Step 1: Check for pointer array initialization
class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { for(int i=0; i<26; i++) children[i] = nullptr; isEnd = false; } }; correctly declares an array of 26 pointers to TrieNode, initialized to nullptr, representing children for each alphabet letter.Step 2: Verify isEnd boolean initialization
class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { for(int i=0; i<26; i++) children[i] = nullptr; isEnd = false; } }; sets isEnd to false in constructor, which marks if a word ends at this node.Final Answer:
class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { for(int i=0; i<26; i++) children[i] = nullptr; isEnd = false; } }; -> Option CQuick Check:
Pointer array + bool flag = class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { for(int i=0; i<26; i++) children[i] = nullptr; isEnd = false; } }; [OK]
- Using int array instead of pointer array
- Not initializing children pointers
- Using map<int> instead of map<char>
class TrieNode {
public:
TrieNode* children[26];
bool isEnd;
TrieNode() {
for(int i=0; i<26; i++) children[i] = nullptr;
isEnd = false;
}
};
class Trie {
public:
TrieNode* root;
Trie() { root = new TrieNode(); }
void insert(string word) {
TrieNode* node = root;
for(char c : word) {
int idx = c - 'a';
if(!node->children[idx])
node->children[idx] = new TrieNode();
node = node->children[idx];
}
node->isEnd = true;
}
bool startsWith(string prefix) {
TrieNode* node = root;
for(char c : prefix) {
int idx = c - 'a';
if(!node->children[idx]) return false;
node = node->children[idx];
}
return true;
}
};
int main() {
Trie trie;
trie.insert("apple");
trie.insert("app");
cout << (trie.startsWith("app") ? "Yes" : "No") << endl;
cout << (trie.startsWith("apl") ? "Yes" : "No") << endl;
return 0;
}Solution
Step 1: Insert words "apple" and "app" into Trie
Both words share prefix "app", so nodes for 'a', 'p', 'p' exist. "apple" adds nodes for 'l' and 'e'.Step 2: Check startsWith for "app" and "apl"
"app" prefix exists, so returns true (prints "Yes"). "apl" prefix does not exist (no 'l' after 'p'), so returns false (prints "No").Final Answer:
Yes No -> Option BQuick Check:
Prefix "app" found, "apl" not found = Yes No [OK]
- Confusing isEnd with prefix existence
- Assuming "apl" is found because 'a' and 'p' exist
- Printing boolean instead of Yes/No
void insert(string word) {
TrieNode* node = root;
for(char c : word) {
int idx = c - 'a';
if(node->children[idx] == nullptr)
node->children[idx] = new TrieNode();
node = node->children[idx];
node->isEnd = true;
}
}Solution
Step 1: Analyze isEnd assignment inside loop
Setting isEnd = true inside the loop marks every node along the word as an end of a word, incorrectly marking prefixes as complete words.Step 2: Correct placement of isEnd assignment
isEnd should be set to true only after the loop ends, marking only the last node as end of the inserted word.Final Answer:
Setting isEnd = true inside the loop causes all prefixes to be marked as words -> Option AQuick Check:
isEnd inside loop = wrong word marking [OK]
- Marking prefixes as complete words
- Forgetting to set isEnd at all
- Incorrect children pointer checks
Solution
Step 1: Locate prefix node in Trie
Traverse the Trie character by character to reach the node representing the last character of the prefix.Step 2: Perform DFS from prefix node to collect words
From this node, recursively explore all child nodes to find all words that start with the prefix, collecting those where isEnd is true.Final Answer:
Traverse Trie to prefix node, then perform DFS to collect all words below it -> Option DQuick Check:
Prefix node + DFS = all autocomplete words [OK]
- Returning only prefix node word ignoring children
- Filtering all words each query (inefficient)
- Using hash map without Trie traversal
Ready to prove it?
Pass the test and this pattern moves unknown
