Longest Word in Dictionary Using Trie in DSA Rust - 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 gets bigger?
Analyze the time complexity of the following code snippet.
struct TrieNode {
children: [Option; 26],
is_word: bool,
}
impl TrieNode {
fn new() -> Self {
TrieNode {
children: Default::default(),
is_word: false,
}
}
fn insert(&mut self, word: &str) {
let mut node = self;
for ch in word.chars() {
let idx = (ch as usize) - ('a' as usize);
if node.children[idx].is_none() {
node.children[idx] = Some(Box::new(TrieNode::new()));
}
node = node.children[idx].as_mut().unwrap();
}
node.is_word = true;
}
}
This code inserts words into a Trie, building a tree where each node represents a letter.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through each character of every word to insert it into the Trie.
- How many times: For each word, the loop runs once per character; for all words, it runs for the total number of characters in all words.
As the number of words and their lengths grow, the total steps increase roughly with the total characters inserted.
| Input Size (n words) | Approx. Operations (total characters) |
|---|---|
| 10 words, avg length 5 | ~50 steps |
| 100 words, avg length 5 | ~500 steps |
| 1000 words, avg length 5 | ~5000 steps |
Pattern observation: The operations grow linearly with the total number of characters inserted.
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: "Inserting words into a Trie is always O(1) because it's a tree."
[OK] Correct: Each word requires steps equal to its length, so longer words take more time, making the time depend on word length, not constant.
Understanding this helps you explain how tries efficiently handle many words and why their insertion time depends on word length, a key insight for many coding problems.
"What if we changed the Trie to store words with uppercase letters as well? How would the time complexity change?"
Practice
Trie data structure when finding the longest word in a dictionary?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 Trie traversal.Final Answer:
To efficiently store and search words by their prefixes -> Option DQuick Check:
Trie = prefix-based search [OK]
- Confusing Trie with sorting structure
- Thinking Trie counts words only
- Assuming Trie reverses words
Solution
Step 1: Understand Trie children representation
Each node has fixed 26 children for letters a-z, stored as array of Options.Step 2: Check Rust syntax correctness
Option allows nullable child nodes; array size 26 matches alphabet.Final Answer:
struct TrieNode { children: [Option; 26] } -> Option AQuick Check:
Fixed-size array with Option<Box> = correct Trie node [OK]
- Using Vec without fixed size wastes memory
- Using HashMap without Box causes ownership errors
- Not wrapping children in Option causes panic
let words = vec!["a", "app", "ap", "apply", "apple"];
let longest = find_longest_word(words);
println!("{}", longest);
Assume find_longest_word uses a Trie to find the longest word where every prefix is also a word.Solution
Step 1: Check prefixes for each word
"apple" prefixes: a, ap, app, appl; "appl" missing, so invalid.
"apply" prefixes: a, ap, app, appl; "appl" missing, invalid.
"app" prefixes: a, ap, app; all present.
"ap" prefixes: a, ap; all present.Step 2: Find longest valid word
"app" is longest word with all prefixes present.Final Answer:
"app" -> Option CQuick Check:
Longest word with all prefixes = "app" [OK]
- Assuming 'apple' or 'apply' are valid without all prefixes
- Ignoring prefix 'appl' missing
- Choosing shortest word instead of longest
fn find_longest_word(words: Vec<&str>) -> String {
let mut trie = Trie::new();
for word in &words {
trie.insert(word);
}
trie.search_longest_word()
}
// Trie methods:
// insert(&self, word: &str)
// search_longest_word(&self) -> String
Solution
Step 1: Analyze method signatures
Inserting words modifies Trie, so insert must take mutable reference &mut self.Step 2: Identify compilation vs runtime issue
Code compiles but insert called with &self means no actual insertion happens, so Trie remains empty.Final Answer:
The insert method should take &mut self, not &self -> Option BQuick Check:
Mutable insert needed for Trie modification [OK]
- Thinking search needs mutable reference
- Making words vector mutable unnecessarily
- Returning wrong type String vs &str
["a", "banana", "app", "appl", "ap", "apply", "apple"]. Using a Trie, which word will be returned as the longest word where every prefix is also a word? Explain why the word "banana" is not chosen.Solution
Step 1: Check prefixes of each candidate word
"apple" prefixes: a, ap, app, appl, apple - all present.
"apply" prefixes: a, ap, app, appl, apply - all present.
"banana" prefixes: b, ba, ban, bana, banan, banana - none except 'b' likely missing.Step 2: Understand why "banana" is excluded
Since prefixes like 'b', 'ba' are not in the word list, "banana" is invalid.
Between "apple" and "apply", both valid, but "apple" is chosen (depends on lex order or implementation).Final Answer:
"apple" because "banana" prefixes are missing -> Option AQuick Check:
Missing prefixes exclude "banana" [OK]
- Assuming "banana" is valid without prefixes
- Confusing "apply" as longer than "apple"
- Ignoring prefix checks for all words
Ready to prove it?
Pass the test and this pattern moves unknown
