Longest Word in Dictionary Using Trie in DSA C++ - Time & Space Complexity
We want to understand how the time needed grows when finding the longest word in a dictionary using a Trie.
How does the number of steps change as the dictionary size and word lengths increase?
Analyze the time complexity of the following code snippet.
struct TrieNode {
TrieNode* children[26] = {nullptr};
bool isEnd = 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->isEnd = true;
}
void dfs(TrieNode* node, TrieNode* root, std::string& current, std::string& longest) {
if (!node) return;
if (node != root && !node->isEnd) return;
if (current.length() > longest.length())
longest = current;
for (int i = 0; i < 26; i++) {
if (node->children[i]) {
current.push_back('a' + i);
dfs(node->children[i], root, current, longest);
current.pop_back();
}
}
}
This code inserts words into a Trie and uses depth-first search to find the longest word where all prefixes are present.
- Primary operation: Insertion loops over each character of every word, and DFS recursively visits Trie nodes.
- How many times: Insert runs once per word, each character once; DFS visits nodes up to total characters in all words.
As the number of words and their lengths grow, the total characters inserted and nodes visited increase roughly with the sum of all word lengths.
| Input Size (n words) | Approx. Operations (sum of word lengths) |
|---|---|
| 10 (avg length 5) | ~50 insert + ~50 DFS steps |
| 100 (avg length 5) | ~500 insert + ~500 DFS steps |
| 1000 (avg length 5) | ~5000 insert + ~5000 DFS steps |
Pattern observation: Operations grow roughly linearly with total characters in all words combined.
Time Complexity: O(N * L)
This means the time grows linearly with the number of words (N) times the average length of each word (L).
[X] Wrong: "The search for the longest word is just O(N) because we only check each word once."
[OK] Correct: We must consider the length of each word and the Trie traversal, so the total steps depend on both number and length of words.
Understanding this complexity helps you explain how tries efficiently handle prefix problems and why they are preferred over simple lists in many cases.
"What if we used a hash set instead of a Trie to check prefixes? How would the time complexity change?"
Practice
Solution
Step 1: Understand Trie usage
A Trie stores words by their prefixes, allowing quick prefix checks.Step 2: Relate to longest word search
Finding the longest word with all prefixes present is easier with prefix-based storage.Final Answer:
To efficiently store and search words by their prefixes -> Option DQuick Check:
Trie = prefix search [OK]
- Thinking Trie sorts words alphabetically
- Confusing Trie with simple arrays
- Assuming Trie counts words only
Solution
Step 1: Check children type and size
Children should be pointers to TrieNode, size 26 for lowercase letters.Step 2: Check initialization
Initialize all children to nullptr and isEnd to false.Final Answer:
class TrieNode { public: TrieNode* children[26]; bool isEnd; TrieNode() { for(int i=0; i<26; i++) children[i] = nullptr; isEnd = false; } }; -> Option BQuick Check:
Pointer array size 26, initialized null [OK]
- Using int array instead of pointers
- Wrong array size (52 instead of 26)
- Not initializing children to nullptr
vector<string> words = {"a", "ap", "app", "appl", "apple", "apply"};
// After building Trie and searching longest word
cout << longestWord(words) << endl;Solution
Step 1: Insert all words into Trie
Words "a", "ap", "app", "appl", "apple", "apply" are inserted.Step 2: Find longest word with all prefixes present
"apple" and "apply" both have all prefixes present, but "apple" comes first lexicographically.Final Answer:
"apple" -> Option AQuick Check:
Longest word with all prefixes = "apple" [OK]
- Choosing "apply" ignoring lex order
- Choosing shorter words like "app"
- Not verifying all prefixes exist
void insert(TrieNode* root, string word) {
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;
}Solution
Step 1: Check index calculation
Index is correctly calculated as c - 'a' for lowercase letters.Step 2: Verify insertion and end marking
Children nodes are created if null, and isEnd is set true after insertion.Final Answer:
No error; code is correct -> Option AQuick Check:
Insertion logic correct [OK]
- Miscomputing index for uppercase letters
- Forgetting to mark isEnd true
- Not initializing root before use (outside snippet)
Solution
Step 1: Understand case sensitivity
Uppercase and lowercase letters differ; need to distinguish them in Trie.Step 2: Adjust Trie structure
Use 52 children per node (26 lowercase + 26 uppercase) to store all letters distinctly.Final Answer:
Use a Trie with 52 children per node to handle both cases distinctly -> Option CQuick Check:
Handle case by expanding Trie size [OK]
- Converting all to lowercase loses case info
- Ignoring case causes wrong matches
- Using hash map loses prefix tree benefits
Ready to prove it?
Pass the test and this pattern moves unknown
