Implement Trie (Prefix Tree) medium
Problem Statement
Implement a Trie (Prefix Tree) data structure.
A Trie is a tree-like data structure used for efficient retrieval of keys in a set of strings. Nodes in a Trie contain a mapping from characters to child nodes. Each path from the root to a leaf represents a word in the Trie.
Implement the Trie
class:
Trie()
Initializes the trie object.void insert(string word)
Inserts the stringword
into the trie.bool search(string word)
Returnstrue
if the stringword
is in the trie (i.e., it was inserted before), andfalse
otherwise.bool startsWith(string prefix)
Returnstrue
if there is any string in the trie that starts with the given prefix, andfalse
otherwise.
Example 1
Input ["Trie", "insert", "search", "search", "startsWith", "insert", "search"] [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
Output [null, null, true, false, true, null, true]
Explanation Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); // return True trie.search("app"); // return False trie.startsWith("app"); // return True trie.insert("app"); trie.search("app"); // return True
Example 2
Input ["Trie", "insert", "search", "startsWith", "startsWith"] [[], ["a"], ["a"], ["a"], ["abc"]]
Output [null, null, true, true, false]
Steps
-
Trie Node Structure: Design a node structure to represent a node in the Trie. It should contain:
- A boolean flag
isWord
indicating whether this node represents the end of a word. - A dictionary (or hash map)
children
to store child nodes, mapping characters to their corresponding nodes.
- A boolean flag
-
insert(word)
: Iterate through the characters of the inputword
. For each character:- If a child node for that character exists, move to that child.
- Otherwise, create a new child node and move to it.
- Once the entire word is processed, mark the last node as
isWord = true
.
-
search(word)
: Similar toinsert
, iterate through the characters of the inputword
. If at any point a child node for a character is not found, returnfalse
. If the entire word is processed, returntrue
only if the last node'sisWord
flag istrue
. -
startsWith(prefix)
: Similar tosearch
, but you don't need to check theisWord
flag at the end. Returntrue
if you reach the end of theprefix
without encountering a missing child node.
Explanation
The Trie data structure efficiently stores strings and allows for fast prefix searches. The key is that the common prefixes are shared among different words. This reduces space compared to storing each word independently, especially when there are many words with common prefixes. The time complexity for insertion, search, and startsWith operations is proportional to the length of the string, making it very efficient for autocompletion and other prefix-based operations.
Code
public class Trie {
private class TrieNode {
public bool isWord;
public Dictionary<char, TrieNode> children;
public TrieNode() {
isWord = false;
children = new Dictionary<char, TrieNode>();
}
}
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void Insert(string word) {
TrieNode node = root;
foreach (char c in word) {
if (!node.children.ContainsKey(c)) {
node.children[c] = new TrieNode();
}
node = node.children[c];
}
node.isWord = true;
}
public bool Search(string word) {
TrieNode node = root;
foreach (char c in word) {
if (!node.children.ContainsKey(c)) {
return false;
}
node = node.children[c];
}
return node.isWord;
}
public bool StartsWith(string prefix) {
TrieNode node = root;
foreach (char c in prefix) {
if (!node.children.ContainsKey(c)) {
return false;
}
node = node.children[c];
}
return true;
}
}
Complexity
-
Time Complexity:
insert(word)
: O(m), where m is the length of the word.search(word)
: O(m)startsWith(prefix)
: O(m)
-
Space Complexity: O(N*M), where N is the number of words inserted, and M is the maximum length of a word. In the worst case (all words are unique and have no common prefixes), the space used will be proportional to the total number of characters in all inserted words.