Implement a Trie Data Structure
The Trie, also known as a prefix tree, is a specialized tree-like data structure that stores a dynamic set or associative array where the keys are usually strings. It's particularly efficient for operations involving prefixes, such as autocomplete suggestions or spell checking. Your task is to implement a Trie data structure from scratch.
Problem Description
You need to implement a Trie class that supports the following operations:
insert(word): Adds awordto the Trie.search(word): Returnstrueif thewordis present in the Trie, andfalseotherwise.startsWith(prefix): Returnstrueif there is anywordin the Trie that starts with the givenprefix, andfalseotherwise.
A Trie node should typically contain:
- A way to store references to its children nodes. This can be an array (if the alphabet size is fixed and small, like 26 for lowercase English letters) or a map/dictionary.
- A flag (boolean) to indicate if the current node marks the end of a complete word.
Key Requirements:
- The Trie should be able to handle words consisting of lowercase English letters ('a' through 'z').
- Implement the
insert,search, andstartsWithmethods as described.
Expected Behavior:
insert: Successfully adds the word. If the word already exists, the Trie should remain unchanged.search: Returnstrueonly if the entire word exists and is marked as a complete word in the Trie.startsWith: Returnstrueif any word or prefix in the Trie matches the givenprefix.
Edge Cases:
- Inserting an empty string.
- Searching for an empty string.
- Searching for a prefix that is also a complete word.
- Words that are prefixes of other words (e.g., inserting "app" and then "apple").
Examples
Example 1:
Trie trie = new Trie();
trie.insert("apple");
boolean result1 = trie.search("apple"); // Output: true
boolean result2 = trie.search("app"); // Output: false
boolean result3 = trie.startsWith("app"); // Output: true
trie.insert("app");
boolean result4 = trie.search("app"); // Output: true
Explanation:
insert("apple"): Adds "apple" to the Trie.search("apple"): Returnstruebecause "apple" is a complete word in the Trie.search("app"): Returnsfalsebecause "app" was inserted as a prefix for "apple", but not as a complete word itself.startsWith("app"): Returnstruebecause there's at least one word ("apple") that starts with "app".insert("app"): Adds "app" as a complete word to the Trie.search("app"): Now returnstruebecause "app" has been explicitly added as a complete word.
Example 2:
Trie trie = new Trie();
trie.insert("bad");
trie.insert("dad");
trie.insert("mad");
boolean result1 = trie.search("pad"); // Output: false
boolean result2 = trie.startsWith("ma"); // Output: true
trie.insert("pad");
boolean result3 = trie.search("pad"); // Output: true
Explanation:
insertcalls add "bad", "dad", and "mad".search("pad"): Returnsfalseas "pad" is not in the Trie.startsWith("ma"): Returnstruebecause "mad" starts with "ma".insert("pad"): Adds "pad" to the Trie.search("pad"): Returnstruebecause "pad" is now a complete word.
Example 3: Edge Cases
Trie trie = new Trie();
trie.insert(""); // Inserting an empty string
boolean result1 = trie.search(""); // Output: true
boolean result2 = trie.startsWith(""); // Output: true
trie.insert("a");
boolean result3 = trie.search("a"); // Output: true
boolean result4 = trie.startsWith("b"); // Output: false
Explanation:
- Inserting an empty string marks the root node as a word ending.
search("")returnstrue.startsWith("")returnstrue(as an empty prefix matches everything).search("a")returnstrueafter inserting "a".startsWith("b")returnsfalseas no word starts with "b".
Constraints
- The Trie will only contain words composed of lowercase English letters ('a'-'z').
- The maximum length of a word or prefix will not exceed 100 characters.
- The total number of words inserted into the Trie will not exceed 10,000.
- The total number of search/startsWith operations will not exceed 10,000.
- Your implementation should aim for an efficient time complexity, ideally O(L) for insert, search, and startsWith, where L is the length of the word/prefix.
Notes
- Consider how you will represent the Trie nodes and their children. A common approach is to use an array of size 26 for children, where each index corresponds to a letter of the alphabet, or a hash map/dictionary for more flexibility.
- Pay close attention to the difference between
search(requiring a complete word) andstartsWith(requiring any word to have the given prefix). - When inserting a word, ensure that the final node for the word is marked as a word ending.
- When checking for a prefix, you only need to traverse the Trie up to the length of the prefix. If the traversal is successful,
startsWithshould returntrue.