Leetcode 208: Implement Trie (Prefix Tree)
Implement a Trie data structure that supports inserting words, searching for exact matches, and checking if any word starts with a given prefix.
Problem
Approach
Steps
Complexity
Input: You are given a list of operations to perform on the Trie data structure. Each operation corresponds to one of the Trie methods: insert, search, or startsWith.
Example: [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
Constraints:
• Each string in the Trie has a length between 1 and 2000.
• All characters in the strings are lowercase English letters.
• At most 30,000 operations will be performed.
Output: The output consists of the results of the operations in the order they are performed. For each 'insert' operation, the output is 'null'. For 'search' and 'startsWith' operations, the output is a boolean value indicating whether the operation was successful.
Example: [null, null, true, false, true, null, true]
Constraints:
Goal: The goal is to implement the Trie class and handle the operations insert, search, and startsWith efficiently.
Steps:
• Use a nested structure where each node contains an array representing child nodes and a boolean flag indicating if the node corresponds to a complete word.
• For 'insert', iterate through each character of the word and create nodes as necessary.
• For 'search', traverse the Trie and check if the entire word exists.
• For 'startsWith', traverse the Trie to check if there exists a word that begins with the given prefix.
Goal: Constraints ensure the number of operations and string lengths are manageable within the problem's limits.
Steps:
• The number of nodes in each string is between 1 and 2000.
• The total number of operations is at most 30,000.
Assumptions:
• All input strings contain only lowercase English letters.
• No cycles are present in the Trie.
• Input: Example 1
• Explanation: In this example, the operations are performed in sequence, with the insertions and searches occurring as expected, demonstrating how the Trie handles prefixes and exact word matches.
• Input: Example 2
• Explanation: This example shows how the Trie works with a simple dataset where only the word 'dog' is inserted and then searched for both fully and by prefix.
Approach: The approach involves implementing the Trie data structure using a node-based approach where each node holds a collection of child nodes and a flag to indicate if it represents a word.
Observations:
• A Trie is ideal for operations like searching and prefix matching due to its tree-like structure.
• We need to efficiently implement the insert, search, and startsWith methods to handle up to 30,000 operations.
Steps:
• Define a Node class that contains an array for child nodes and a boolean flag indicating if the node represents a complete word.
• Define a Trie class that manages the root node and implements the insert, search, and startsWith operations.
• For insert, iterate over the word and create new nodes if necessary.
• For search, check if all characters of the word exist and if the node at the last character is marked as a complete word.
• For startsWith, traverse the Trie to check if the prefix exists.
Empty Inputs:
• If no words are inserted, all search and startsWith operations should return false.
Large Inputs:
• Handle cases where strings are of maximum length (2000 characters) and when the number of operations is large (30,000).
Special Values:
• Handle cases where words have overlapping prefixes, like 'app' and 'apple'.
Constraints:
• Ensure that the solution performs efficiently within the given constraints.
vector<Node*> chd;
bool isWord;
Node(int n) {
chd.resize(n, 0);
isWord = false;
}
};
class Trie {
public:
Node* root;
Trie() {
root = new Node(26);
}
void insert(string word) {
Node* node = root;
for(int i = 0; i < word.size(); i++) {
if(node->chd[word[i] - 'a'] == NULL)
node->chd[word[i] - 'a'] = new Node(26);
node = node->chd[word[i] - 'a'];
}
node->isWord = true;
}
bool search(string word) {
Node* node = root;
for(int i = 0; i < word.size(); i++) {
if(node->chd[word[i] - 'a'] == NULL)
return false;
node = node->chd[word[i] - 'a'];
}
return node->isWord;
}
bool startsWith(string word) {
Node* node = root;
for(int i = 0; i < word.size(); i++) {
if(node->chd[word[i] - 'a'] == NULL)
return false;
node = node->chd[word[i] - 'a'];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
1 : Variable Initialization
vector<Node*> chd;
Create a vector of Node pointers, `chd`, to represent the child nodes of each node in the Trie.
2 : Variable Initialization
bool isWord;
Declare a boolean variable `isWord` to mark whether the current node is the end of a word.
3 : Constructor
Node(int n) {
Constructor for the Node class. It initializes the `chd` vector and sets `isWord` to `false`.
4 : Node Initialization
chd.resize(n, 0);
Resize the `chd` vector to hold `n` child nodes, initializing each one to `NULL`.
5 : Node Initialization
isWord = false;
Initialize the `isWord` variable to `false`, indicating that the node does not yet represent the end of a word.
6 : Trie Class Definition
class Trie {
Begin the definition of the Trie class.
7 : Access Specifier
public:
Define the public section of the Trie class, making methods accessible from outside the class.
8 : Variable Declaration
Node* root;
Declare a pointer `root` that will point to the root of the Trie.
9 : Constructor
Trie() {
Constructor for the Trie class. It initializes the `root` node of the Trie.
10 : Node Initialization
root = new Node(26);
Initialize the root node of the Trie with 26 child nodes (one for each letter of the alphabet).
11 : End Constructor
}
End the constructor for the Trie class.
12 : Insert Method
void insert(string word) {
Define the insert method to add a word to the Trie.
13 : Variable Declaration
Node* node = root;
Initialize a pointer `node` to the root node of the Trie.
14 : Loop Iteration
for(int i = 0; i < word.size(); i++) {
Loop through each character in the word to insert it into the Trie.
15 : Conditional Check
if(node->chd[word[i] - 'a'] == NULL)
Check if the current node does not have a child node for the character. If so, create a new child node.
16 : Node Initialization
node->chd[word[i] - 'a'] = new Node(26);
Create a new node for the current character and assign it to the corresponding child in the `chd` vector.
17 : Node Update
node = node->chd[word[i] - 'a'];
Move the `node` pointer to the next child node.
18 : Word Marking
node->isWord = true;
Mark the current node as the end of a word by setting `isWord` to `true`.
19 : Search Method
bool search(string word) {
Define the search method to check if a word exists in the Trie.
20 : Variable Declaration
Node* node = root;
Initialize a pointer `node` to the root node of the Trie.
21 : Loop Iteration
for(int i = 0; i < word.size(); i++) {
Loop through each character in the word to check if it exists in the Trie.
22 : Conditional Check
if(node->chd[word[i] - 'a'] == NULL)
Check if the current node does not have a child node for the character. If so, return `false`.
23 : Return False
return false;
Return `false` if any character is not found in the Trie.
24 : Node Update
node = node->chd[word[i] - 'a'];
Move the `node` pointer to the next child node.
25 : Return Result
return node->isWord;
Return `true` if the current node marks the end of a word, otherwise `false`.
26 : StartsWith Method
bool startsWith(string word) {
Define the startsWith method to check if any word in the Trie starts with the given prefix.
27 : Variable Declaration
Node* node = root;
Initialize a pointer `node` to the root node of the Trie.
28 : Loop Iteration
for(int i = 0; i < word.size(); i++) {
Loop through each character in the word to check if it exists as a prefix in the Trie.
29 : Conditional Check
if(node->chd[word[i] - 'a'] == NULL)
Check if the current node does not have a child node for the character. If so, return `false`.
30 : Return False
return false;
Return `false` if the prefix is not found in the Trie.
31 : Node Update
node = node->chd[word[i] - 'a'];
Move the `node` pointer to the next child node.
32 : Return Result
return true;
Return `true` if the prefix exists in the Trie.
Best Case: O(1) for operations on short words or prefixes that are already in the Trie.
Average Case: O(L) where L is the length of the word or prefix being processed.
Worst Case: O(L) for insert, search, and startsWith where L is the length of the longest word or prefix.
Description: Each operation (insert, search, startsWith) depends linearly on the length of the word or prefix being processed.
Best Case: O(L), where L is the length of the longest word being inserted or searched.
Worst Case: O(N * L), where N is the number of words and L is the length of the longest word.
Description: The space complexity depends on the number of nodes created for each inserted word and the maximum length of the words.
LeetCode Solutions Library / DSA Sheets / Course Catalog |
---|
comments powered by Disqus