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.
How does the time to find words grow as we add more words or type longer prefixes?
Analyze the time complexity of the following code snippet.
public List<string> GetWordsWithPrefix(string prefix) {
TrieNode node = root;
foreach (char c in prefix) {
if (!node.Children.ContainsKey(c)) return new List<string>();
node = node.Children[c];
}
return CollectAllWords(node, prefix);
}
private List<string> CollectAllWords(TrieNode node, string prefix) {
List<string> results = new List<string>();
if (node.IsWord) results.Add(prefix);
foreach (var child in node.Children) {
results.AddRange(CollectAllWords(child.Value, prefix + child.Key));
}
return results;
}
This code finds all words in the Trie that start with a given prefix.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Traversing the prefix characters once, then recursively visiting all child nodes under that prefix.
- How many times: The prefix traversal runs once per character in the prefix (length p). The recursive calls visit all descendant nodes under the prefix node, depending on how many words share that prefix.
The time to find the prefix node grows linearly with prefix length.
After that, the time depends on how many words share that prefix, because we collect all those words.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 words, prefix length 3 | 3 steps to find prefix + visits to matching words (few) |
| 100 words, prefix length 3 | 3 steps + more recursive visits if many words share prefix |
| 1000 words, prefix length 3 | 3 steps + many recursive visits if prefix is common |
Pattern observation: The prefix search is fast and depends only on prefix length, but collecting words depends on how many words share that prefix.
Time Complexity: O(p + m)
This means the search takes time proportional to the prefix length plus the number of matching words found.
[X] Wrong: "The search time depends only on the total number of words in the Trie."
[OK] Correct: Actually, the search depends on prefix length and how many words share that prefix, not all words in the Trie.
Understanding this time complexity helps you explain how autocomplete systems stay fast even with many words, which is a common real-world problem.
"What if we limited the number of suggestions returned to a fixed number k? How would the time complexity change?"
Practice
Trie over a HashSet for implementing an autocomplete system?Solution
Step 1: Understand autocomplete requirement
Autocomplete needs to find all words that start with a given prefix quickly.Step 2: Compare Trie vs HashSet
Trie stores characters level by level, so prefix traversal is O(prefix length). HashSet requires scanning all words to check which ones start with the prefix.Final Answer:
Trie supports prefix-based search efficiently, while HashSet requires checking all words -> Option BQuick Check:
Trie = prefix search in O(m), HashSet = O(n*m) for prefix [OK]
- Assuming Trie always uses less memory - it can use more for sparse word sets
- Thinking exact lookup is why Trie beats HashSet - HashSet is actually faster for exact lookup
- Assuming Trie sorts automatically - it doesn't sort, traversal order depends on children insertion
Dictionary<char, TrieNode> Children and a bool IsEndOfWord?Solution
Step 1: Check if child exists
Before creating a new node, check if the character key already exists in Children to avoid overwriting existing subtrees.Step 2: Create only if missing
Using ContainsKey check ensures we reuse existing nodes and only create new ones when the character path doesn't exist yet.Final Answer:
if (!node.Children.ContainsKey(c)) node.Children[c] = new TrieNode(); node = node.Children[c]; -> Option BQuick Check:
Always check ContainsKey before adding to avoid overwriting existing paths [OK]
- Option A overwrites existing children, destroying previously inserted words
- Option C uses Add which throws if key already exists
- Option D stores char as value instead of TrieNode
"ca" in a Trie containing ["cat", "car", "card", "dog"]?List<string> results = new List<string>();
void DFS(TrieNode node, string current) {
if (node.IsEndOfWord) results.Add(current);
foreach (var kvp in node.Children)
DFS(kvp.Value, current + kvp.Key);
}
TrieNode prefixNode = GetPrefixNode("ca");
if (prefixNode != null) DFS(prefixNode, "ca");Solution
Step 1: Find prefix node
GetPrefixNode("ca") traverses c→a and returns the node at 'a'. "dog" is unreachable from this node.Step 2: DFS from prefix node
DFS explores all children from 'a': t (cat), r (car), r→d (card). All three have IsEndOfWord=true.Final Answer:
["cat", "car", "card"] -> Option CQuick Check:
Only words under prefix "ca" subtree are returned, "dog" excluded [OK]
- Including 'dog' - it's in a completely separate Trie branch
- Returning only 'ca' - 'ca' itself is not marked IsEndOfWord
- Returning empty - prefix 'ca' exists in the Trie so DFS finds words
bool StartsWith(string prefix) {
TrieNode node = root;
foreach (char c in prefix) {
if (!node.Children.ContainsKey(c))
return true;
node = node.Children[c];
}
return false;
}Solution
Step 1: Trace the logic
When a character is not found in Children, it means the prefix doesn't exist - should return false, not true. And after traversing all prefix characters successfully, the prefix exists - should return true.Step 2: Identify the swap
The return values are swapped: 'return true' and 'return false' are in each other's place.Final Answer:
Should return false when character not found, and true at the end -> Option AQuick Check:
Missing prefix char = false, full prefix traversed = true [OK]
- Thinking Dictionary is wrong - Dictionary is correct for Children
- Checking IsEndOfWord - StartsWith only checks if prefix path exists, not word end
- Starting from last character - prefix traversal always goes left to right
p, where k is the number of matching words and m is the average word length?Solution
Step 1: Cost of reaching prefix node
Traversing p characters to reach the prefix node costs O(p).Step 2: Cost of DFS to collect results
DFS visits all nodes under the prefix subtree. With k matching words of average length m, this costs O(k * m) in the worst case.Final Answer:
O(p + k * m) -> Option BQuick Check:
Prefix traversal O(p) + result collection O(k*m) = O(p + k*m) [OK]
- O(n) - Trie doesn't scan all words, only the prefix subtree
- O(1) - result collection depends on number and length of matching words
- O(p*n) - that's the HashSet approach, not Trie
Ready to prove it?
Pass the test and this pattern moves unknown
