Implement Trie (Prefix Tree)

Problem Overview
The LeetCode problem "Implement Trie (Prefix Tree)" focuses on constructing and manipulating a trie data structure. A trie is an efficient, tree-based data structure commonly used for storing and searching strings, especially in scenarios involving prefixes. The task requires implementing the trie with the following operations:
- Insert: Insert a word into the trie.
- Search: Search for a complete word in the trie.
- StartsWith: Determine if there is any word in the trie that starts with a given prefix.
The problem provides a foundational understanding of how tries function and are implemented, particularly for applications involving autocomplete, spell-checking, and dictionary searches.
Understanding the Problem
To solve this problem, you must create a class Trie that includes methods to insert, search, and check for prefixes in a collection of strings. Each method needs to efficiently navigate the trie structure both in terms of time and memory.
Example
Assume the following sequence of operations:
- Insert "apple"
- Search "apple" (returns
true) - Search "app" (returns
false) - StartsWith "app" (returns
true) - Insert "app"
- Search "app" (returns
true)
This sequence illustrates how insertions change the state of the trie and how search and prefix operations explore its structure.
Key DSA Concepts and Theory
Trie Data Structure
A trie (pronounced "try") is a type of search tree used to store a dynamic set or associative array where the keys are usually strings. Unlike a binary search tree, no node in the trie stores the key associated with that node; instead, its position in the tree defines the key with which it is associated. All the descendants of a node have a common prefix of the string associated with that node, and the root node is associated with the empty string.
Advantages
- Prefix Searches: Efficiently handles queries related to prefix searches.
- Space Efficiency: Highly space-efficient if the stored strings share prefixes.
- Auto-completion: Perfect base for implementing autocomplete features.
Trie Structure
Each node in a trie typically consists of:
- A collection of child nodes.
- A boolean flag
isEndOfWordthat marks the end of a particular string.
Solution Approach
To implement a trie, a class Trie is created with fundamental methods like insert, search, and startsWith. Each node in the trie is represented by a helper class TrieNode having an array of child nodes and a boolean to determine the end of a word.
Step-by-Step Approach
C++ Implementation
class TrieNode {
public:
TrieNode* children[26];
bool isEndOfWord;
TrieNode() {
for (int i = 0; i < 26; i++) {
children[i] = nullptr;
}
isEndOfWord = false;
}
};
class Trie {
private:
TrieNode* root;
public:
Trie() {
root = new TrieNode();
}
void insert(string word) {
TrieNode* currentNode = root;
for (char c : word) {
int index = c - 'a';
if (currentNode->children[index] == nullptr) {
currentNode->children[index] = new TrieNode();
}
currentNode = currentNode->children[index];
}
currentNode->isEndOfWord = true;
}
bool search(string word) {
TrieNode* currentNode = root;
for (char c : word) {
int index = c - 'a';
if (currentNode->children[index] == nullptr) {
return false;
}
currentNode = currentNode->children[index];
}
return currentNode->isEndOfWord;
}
bool startsWith(string prefix) {
TrieNode* currentNode = root;
for (char c : prefix) {
int index = c - 'a';
if (currentNode->children[index] == nullptr) {
return false;
}
currentNode = currentNode->children[index];
}
return true;
}
};
Java Implementation
class TrieNode {
TrieNode[] children;
boolean isEndOfWord;
TrieNode() {
children = new TrieNode[26];
isEndOfWord = false;
}
}
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode currentNode = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (currentNode.children[index] == null) {
currentNode.children[index] = new TrieNode();
}
currentNode = currentNode.children[index];
}
currentNode.isEndOfWord = true;
}
public boolean search(String word) {
TrieNode currentNode = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (currentNode.children[index] == null) {
return false;
}
currentNode = currentNode.children[index];
}
return currentNode.isEndOfWord;
}
public boolean startsWith(String prefix) {
TrieNode currentNode = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (currentNode.children[index] == null) {
return false;
}
currentNode = currentNode.children[index];
}
return true;
}
}
Time and Space Complexity Analysis
- Insert Operation: The time complexity is O(m), where m is the length of the word being inserted. This is because we potentially need to insert up to m new nodes. The space complexity is O(m) for the newly inserted nodes.
- Search Operation: The time complexity is O(m), where m is the length of the word being searched since it takes m steps to traverse the word. The space complexity is O(1) since no additional space is required beyond the current trie nodes.
- StartsWith Operation: Similar to search, the time complexity is O(m), and the space complexity is O(1).
Common Mistakes to Avoid
- Incorrect Node Initialization: Ensure that all node pointers are initialized correctly to avoid segmentation faults or null pointer exceptions.
- Character Index Calculation: Remember to correctly calculate the index for child nodes using the formula
c - 'a'. - Handling Case Sensitivity: By default, the given implementations assume lowercase alphabets. Adjust the node array size and index calculation if handling a broader set of characters.
Similar Problems on LeetCode
- 211. Add and Search Word - Data structure design
- 642. Design Search Autocomplete System
- 745. Prefix and Suffix Search
Additional Resources and References
- Trie - Wikipedia: Detailed explanation and background on tries.
- GeeksforGeeks - Trie: Overview and examples on trie implementation.
- Introduction to Tries: Tutorial on trie structures and their applications.
By carefully implementing and understanding the trie operations, you can handle a wide variety of search-related problems efficiently, leveraging the strengths of this powerful data structure.