Trie Fundamentals
A trie (prefix tree) is a tree-like data structure for storing strings. Each node represents a character, and paths from root to marked nodes form words.
Node Structure
class TrieNode {
TrieNode[] children;
boolean isEndOfWord;
public TrieNode() {
children = new TrieNode[26]; // lowercase letters
isEndOfWord = false;
}
}
Visual Example
Insert: "apple", "app", "banana"
(root)
/ \
a b
| |
p a
/ \\ |
p n n
| | |
l a a
| | |
e* a* n*
* = end of word marker
Trie Class Implementation
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// Insert a word - O(m) where m = word length
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEndOfWord = true;
}
// Search for exact word - O(m)
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEndOfWord;
}
// Check if any word starts with prefix - O(m)
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
private TrieNode searchPrefix(String prefix) {
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) return null;
node = node.children[index];
}
return node;
}
}
Delete Operation
public void delete(String word) {
deleteHelper(root, word, 0);
}
private boolean deleteHelper(TrieNode node, String word, int index) {
if (index == word.length()) {
if (!node.isEndOfWord) return false;
node.isEndOfWord = false;
return isEmpty(node); // Can delete this node if no children
}
int charIndex = word.charAt(index) - 'a';
TrieNode child = node.children[charIndex];
if (child == null) return false;
boolean shouldDeleteChild = deleteHelper(child, word, index + 1);
if (shouldDeleteChild) {
node.children[charIndex] = null;
return isEmpty(node) && !node.isEndOfWord;
}
return false;
}
private boolean isEmpty(TrieNode node) {
for (TrieNode child : node.children) {
if (child != null) return false;
}
return true;
}
Complexity
| Operation | Time | Space |
|---|---|---|
| Insert | O(m) | O(m) |
| Search | O(m) | O(1) |
| Delete | O(m) | O(1) |
| startsWith | O(m) | O(1) |
where m = length of word/prefix
Space: O(N × m) total where N = number of words, m = avg length. In practice, shared prefixes save space.
Trie Applications
Autocomplete / Word Search II
Find all words in trie with given prefix:
public List<String> autocomplete(TrieNode root, String prefix) {
List<String> results = new ArrayList<>();
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) return results;
node = node.children[index];
}
collectWords(node, prefix, results);
return results;
}
private void collectWords(TrieNode node, String current, List<String> results) {
if (node == null) return;
if (node.isEndOfWord) results.add(current);
for (int i = 0; i < 26; i++) {
if (node.children[i] != null) {
collectWords(node.children[i], current + (char)('a' + i), results);
}
}
}
Word Dictionary (Search with Dots)
class WordDictionary {
TrieNode root;
public WordDictionary() {
root = new TrieNode();
}
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEndOfWord = true;
}
public boolean search(String word) {
return searchHelper(root, word, 0);
}
private boolean searchHelper(TrieNode node, String word, int index) {
if (node == null) return false;
if (index == word.length()) return node.isEndOfWord;
char c = word.charAt(index);
if (c == '.') {
for (TrieNode child : node.children) {
if (searchHelper(child, word, index + 1)) return true;
}
return false;
} else {
return searchHelper(node.children[c - 'a'], word, index + 1);
}
}
}
Longest Common Prefix
public String longestCommonPrefix(String[] strs) {
if (strs.length == 0) return "";
TrieNode root = new TrieNode();
for (String s : strs) insert(root, s);
String prefix = "";
TrieNode node = root;
while (hasSingleChild(node)) {
for (int i = 0; i < 26; i++) {
if (node.children[i] != null) {
prefix += (char)('a' + i);
node = node.children[i];
break;
}
}
}
return prefix;
}
private boolean hasSingleChild(TrieNode node) {
int count = 0;
for (TrieNode child : node.children) {
if (child != null) count++;
}
return count == 1 && !node.isEndOfWord;
}
When to Use Trie
- Prefix-based problems: autocomplete, prefix matching
- Word games: Scrabble, Boggle, word search
- String matching: find all words matching pattern
- IP routing: longest prefix match
Trie vs HashMap
| Feature | Trie | HashMap |
|---|---|---|
| Prefix search | O(m) native | O(n × m) |
| Space | Shared prefixes | Full strings |
| Insert/Search | O(m) | O(m) average |
| Range queries | Efficient | Not supported |
Practice Problems
Implement a trie with insert, search, and startsWith methods.
Example:
Input: Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); trie.startsWith("app");
Output: true, true
Standard trie operations.
Optimal Solution — O(m) time, O(N × m) space
Standard trie with array of children
class Trie {
TrieNode root;
class TrieNode { TrieNode[] children = new TrieNode[26]; boolean isEnd = false; }
public Trie() { root = new TrieNode(); }
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) node.children[i] = new TrieNode();
node = node.children[i];
}
node.isEnd = true;
}
public boolean search(String word) {
TrieNode node = find(word);
return node != null && node.isEnd;
}
public boolean startsWith(String prefix) { return find(prefix) != null; }
private TrieNode find(String s) {
TrieNode node = root;
for (char c : s.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) return null;
node = node.children[i];
}
return node;
}
}Edge Cases:
- Empty string
- Prefix is also a word
- All same prefix
Given an m x n board of characters and a list of words, return all words on the board. Each word must be constructed from letters of sequentially adjacent cells.
Example:
Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]
Found words 'eat' and 'oath' on the board.
Optimal Solution — O(M × N × 4^L) where L = max word length time, O(K × L) for trie where K = words space
Build trie from words, then DFS on board using trie for efficient pruning
class Solution {
public List<String> findWords(char[][] board, String[] words) {
TrieNode root = buildTrie(words);
List<String> result = new ArrayList<>();
for (int i = 0; i < board.length; i++)
for (int j = 0; j < board[0].length; j++)
dfs(board, root, i, j, result);
return result;
}
private void dfs(char[][] board, TrieNode node, int r, int c, List<String> result) {
if (r < 0 || r >= board.length || c < 0 || c >= board[0].length) return;
char ch = board[r][c];
if (ch == '#' || node.children[ch - 'a'] == null) return;
node = node.children[ch - 'a'];
if (node.word != null) {
result.add(node.word);
node.word = null;
}
board[r][c] = '#';
dfs(board, node, r+1, c, result);
dfs(board, node, r-1, c, result);
dfs(board, node, r, c+1, result);
dfs(board, node, r, c-1, result);
board[r][c] = ch;
}
class TrieNode { TrieNode[] children = new TrieNode[26]; String word; }
private TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String w : words) {
TrieNode node = root;
for (char c : w.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) node.children[i] = new TrieNode();
node = node.children[i];
}
node.word = w;
}
return root;
}
}Edge Cases:
- No words found
- All cells used for one word
- Multiple words share path
Quiz
1. What is the time complexity of inserting a word of length m into a trie?
2. Why is a trie more space-efficient than storing full strings in a hash set?
3. What is the primary purpose of Trie (Prefix Tree)?
4. What is a common mistake when implementing Trie (Prefix Tree)?
Flashcards
Question
What is a trie and when should you use it?
Click to reveal answer
Answer
A trie (prefix tree) stores strings character-by-character. Use it for prefix-based problems: autocomplete, word matching, longest common prefix. O(m) insert/search where m = word length.
Question
How do you handle '.' (wildcard) in trie search?
Click to reveal answer
Answer
When encountering '.', try all 26 children recursively. Return true if any path matches. This is used in Word Dictionary problems.
Question
What is Trie (Prefix Tree)?
Click to reveal answer
Answer
Trie (Prefix Tree) is a key concept in software engineering.
Question
When to use Trie (Prefix Tree)?
Click to reveal answer
Answer
Use Trie (Prefix Tree) when building production systems that require reliability, scalability, and maintainability.
Question
Trie (Prefix Tree) best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.Trie enables O(m) prefix operations where m is word length
- 2.Each node stores 26 children (for lowercase) and an end-of-word flag
- 3.Tries excel at prefix-based problems and share common prefixes
- 4.Wildcard search explores all children at '.' positions
Interview Tips
- •Start with basic insert/search, then add features as needed
- •For wildcard problems, use recursive DFS at '.' positions
- •Mention space optimization: shared prefixes save memory
- •Know when trie is better than hash set (prefix queries vs exact match)
Cheat Sheet
Trie Cheat Sheet
Node Structure:
class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEndOfWord = false;
}
Core Operations:
- Insert: O(m) - traverse/create nodes for each character
- Search: O(m) - traverse nodes, check isEndOfWord
- startsWith: O(m) - traverse nodes only
Standard Template:
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) node.children[i] = new TrieNode();
node = node.children[i];
}
node.isEndOfWord = true;
}
Applications:
- Autocomplete / Prefix search
- Word search with wildcards ('.')
- Longest common prefix
- Spell checker
- IP routing (longest prefix match)
Space: O(N × m) where N = words, m = avg length. Shared prefixes save space.
Trie vs HashMap:
- Trie: native prefix search, shared storage
- HashMap: O(1) exact lookup, more memory for prefixes