Longest Word in Dictionary Using Trie in DSA Go - Time & Space Complexity
We want to understand how the time needed to find the longest word in a dictionary using a Trie grows as the dictionary size increases.
Specifically, how does the search and insertion time change when we add more words?
Analyze the time complexity of the following code snippet.
// Insert words into Trie
for _, word := range words {
node := root
for _, ch := range word {
if node.children[ch-'a'] == nil {
node.children[ch-'a'] = &TrieNode{}
}
node = node.children[ch-'a']
}
node.isWord = true
}
// Find longest word by DFS
func dfs(node *TrieNode, path string) string {
longest := path
for i, child := range node.children {
if child != nil && child.isWord {
candidate := dfs(child, path+string('a'+i))
if len(candidate) > len(longest) || (len(candidate) == len(longest) && candidate < longest) {
longest = candidate
}
}
}
return longest
}
This code inserts all words into a Trie and then uses depth-first search to find the longest word where every prefix is also a word.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Insertion loops over each character of every word, and DFS recursively explores Trie nodes.
- How many times: Insertion runs once per word and per character; DFS visits nodes that represent valid words.
As the number of words (n) and their average length (m) grow, insertion takes longer because each character is processed.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 words, avg length 5 | ~50 insert steps + DFS visits |
| 100 words, avg length 5 | ~500 insert steps + DFS visits |
| 1000 words, avg length 5 | ~5000 insert steps + DFS visits |
Pattern observation: Operations grow roughly proportional to total characters inserted and nodes visited during DFS.
Time Complexity: O(n * m)
This means the time grows linearly with the total number of characters in all words combined.
[X] Wrong: "The search for the longest word is just O(n) because we only check each word once."
[OK] Correct: Each word insertion processes all its characters, and DFS explores nodes based on prefixes, so the total work depends on all characters, not just word count.
Understanding how Trie operations scale helps you explain efficient prefix searches and word validations, a useful skill in many coding challenges.
"What if we used a hash set instead of a Trie for prefix checks? How would the time complexity change?"
Practice
Solution
Step 1: Understand Trie usage
A Trie stores words by breaking them into characters and linking nodes for prefixes.Step 2: Connect Trie to longest word search
This structure allows quick prefix checks, helping find the longest word built from other words.Final Answer:
To efficiently store and search words by their prefixes -> Option DQuick Check:
Trie = prefix search [OK]
- Confusing Trie with sorting algorithms
- Thinking Trie reverses words
- Assuming Trie counts words only
Solution
Step 1: Identify correct children type
Children should map characters (rune) to TrieNode pointers for branching.Step 2: Check isWord type
isWord is a boolean indicating if node ends a valid word.Final Answer:
type TrieNode struct { children map[rune]*TrieNode; isWord bool } -> Option CQuick Check:
Children map[rune]*TrieNode and bool isWord [OK]
- Using slices instead of maps for children
- Wrong data types for children or isWord
- Using int or string instead of rune keys
words := []string{"a", "app", "apple", "apply", "ap"}
trie := NewTrie()
for _, w := range words {
trie.Insert(w)
}
result := trie.LongestWord()
fmt.Println(result)Solution
Step 1: Insert words into Trie
All words "a", "ap", "app", "apple", "apply" are inserted.Step 2: Find longest word built from prefixes
"app" has all prefixes "a", "ap", "app" present as words. "apple" and "apply" lack "appl" prefix.Final Answer:
"app" -> Option AQuick Check:
Longest word with all prefixes = "app" [OK]
- Choosing "apple" or "apply" ignoring missing "appl"
- Picking lexicographically later word
- Not inserting all words before search
func (t *Trie) Insert(word string) {
node := t.root
for _, ch := range word {
if node.children[ch] == nil {
node.children[ch] = &TrieNode{}
}
node = node.children[ch]
}
node.isWord = false
}Solution
Step 1: Check isWord assignment
isWord marks end of a valid word and must be true, not false.Step 2: Understand impact on longest word search
Setting isWord false means no word end is recognized, breaking longest word logic.Final Answer:
isWord should be set to true, not false -> Option AQuick Check:
isWord = true marks word end [OK]
- Setting isWord false instead of true
- Not initializing children map before use
- Ignoring rune type for characters
Solution
Step 1: Check prefixes for each candidate
"world" has prefixes "w", "wo", "wor", "worl" all present.Step 2: Check "banana" and its prefixes
"banana" lacks prefixes like "b", "ba", so it fails.Step 3: Compare longest valid words
"world" (length 5) is longest with all prefixes present; "banana" (6) invalid, "banan" also invalid.Final Answer:
"world" -> Option BQuick Check:
Longest word with all prefixes = "world" [OK]
- Ignoring missing prefixes for banana
- Choosing shorter words
- Not applying lexicographical tie-break
Ready to prove it?
Pass the test and this pattern moves unknown
