Problem Statement
A Trie (Prefix Tree) is a tree data structure used to efficiently store and search strings.
Implement a Trie with the following operations:
insert(String word)→ Inserts a word into the trie.search(String word)→ Returns true if the word is in the trie.startsWith(String prefix)→ Returns true if there is any word in the trie that starts with the given prefix.
Example:
Input: ["Trie","insert","search","search","startsWith","insert","search"] [[],["apple"],["apple"],["app"],["app"],["app"],["app"]] Output: [null,null,true,false,true,null,true]
Approach to Solve the Problem (Simple Language)
Idea:
- A Trie is a tree of nodes where each node represents a character.
- Each node has:
- An array/map of children nodes (for all possible next characters)
- A boolean flag to mark the end of a word
Operations:
- Insert:
- Start from root
- For each character in the word:
- If the character node does not exist, create it
- Move to the child node
- Mark the last node as end of word
- Search:
- Start from root
- Traverse each character of the word
- If character node does not exist → return false
- After last character, check isEndOfWord → return true if word exists
- StartsWith:
- Start from root
- Traverse each character of the prefix
- If character node does not exist → return false
- If traversal succeeds → return true (prefix exists)
Why Trie works efficiently:
- Searching a word or prefix is O(L), where L = length of the word/prefix, independent of number of words.
Java Code Solution
class TrieNode {
TrieNode[] children = new TrieNode[26]; // For lowercase letters a-z
boolean isEndOfWord = false;
}
public class Trie {
private final TrieNode root;
public Trie() {
root = new TrieNode();
}
// Insert a word into trie
public void insert(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
int index = ch - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEndOfWord = true;
}
// Search for a word in trie
public boolean search(String word) {
TrieNode node = searchNode(word);
return node != null && node.isEndOfWord;
}
// Check if any word starts with prefix
public boolean startsWith(String prefix) {
TrieNode node = searchNode(prefix);
return node != null;
}
// Helper method to traverse trie for a word/prefix
private TrieNode searchNode(String s) {
TrieNode node = root;
for (char ch : s.toCharArray()) {
int index = ch - 'a';
if (node.children[index] == null) return null;
node = node.children[index];
}
return node;
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("apple");
System.out.println(trie.search("apple")); // true
System.out.println(trie.search("app")); // false
System.out.println(trie.startsWith("app")); // true
trie.insert("app");
System.out.println(trie.search("app")); // true
}
}
Dry Run Example
Input:
Operations: insert("apple"), search("apple"), search("app"), startsWith("app"), insert("app"), search("app")
Step-by-step Execution:
- insert(“apple”)
- Traverse root → create nodes for ‘a’ → ‘p’ → ‘p’ → ‘l’ → ‘e’
- Mark ‘e’ node as end of word
- search(“apple”)
- Traverse nodes: ‘a’ → ‘p’ → ‘p’ → ‘l’ → ‘e’
- End node isEndOfWord = true → return true
- search(“app”)
- Traverse nodes: ‘a’ → ‘p’ → ‘p’
- End node isEndOfWord = false → return false
- startsWith(“app”)
- Traverse nodes: ‘a’ → ‘p’ → ‘p’
- Node exists → return true
- insert(“app”)
- Traverse nodes: ‘a’ → ‘p’ → ‘p’ (already exist)
- Mark last ‘p’ as end of word
- search(“app”)
- Traverse nodes: ‘a’ → ‘p’ → ‘p’
- isEndOfWord = true → return true
Textual Diagram for Understanding
Insert "apple":
root
|
'a'
|
'p'
|
'p'
|
'l'
|
'e' (endOfWord=true)
Insert "app":
'a'->'p'->'p' (mark 'p' as endOfWord=true)
Trie structure allows:
search("apple") → true
search("app") → true after inserting "app"
startsWith("app") → true
Complexity Analysis
- Time Complexity:
- Insert: O(L)
- Search: O(L)
- StartsWith: O(L)
- L = length of the word/prefix
- Space Complexity: O(N * L)
- N = number of words, L = average length (each node can have up to 26 children)
