Leetcode 208. Implement Trie (Prefix Tree)
Problem Explanation:
The problem is to implement a specialized kind of tree data structure called a Trie, and provide it with three functions: insert
, search
, and startsWith
. A Trie, also known as a Prefix Tree, is a kind of search tree that is used to store a dynamic set or associative array where the keys are usually strings. Unlike a binary search tree, no node in the tree stores the key associated with that node; instead, its position in the tree defines the key with which it is associated.
Example:
For instance, let's take the Trie instance trie
to which the string "apple" is inserted. Now, the Trie object should have the "apple" string stored in it. Thus, running a search function for the string "apple" should return true while searching string "app" should return false as it hasn't been inserted into the Trie. However, the startsWith
function should return true when queried with "app" as the string "apple" which is present in the Trie starts with "app".
Solution Approach:
The solution uses a TrieNode struct to represent each node in the Trie. Each TrieNode contains an array of children
, which are points to other TrieNodes, and a boolean value isWord
, which is true when the TrieNode marks the end of a valid word.
The insert
function iterates over each character in the input word and navigates the Trie accordingly, creating new TrieNodes as necessary, and sets isWord
to true at the final character.
The search
function is similar, but instead of creating new nodes, it returns false if it fails to find the next node. It also checks that the isWord
of the final character is set to true.
The startsWith
function is identical to search
, but doesn't check that isWord
is true.
C++ Solution:
1
2c++
3struct TrieNode {
4 vector<shared_ptr<TrieNode>> children;
5 bool isWord = false;
6 TrieNode() : children(26) {}
7};
8
9class Trie {
10 public:
11 void insert(const string& word) {
12 shared_ptr<TrieNode> node = root;
13 for (const char c : word) {
14 const int i = c - 'a';
15 if (node->children[i] == nullptr)
16 node->children[i] = make_shared<TrieNode>();
17 node = node->children[i];
18 }
19 node->isWord = true;
20 }
21
22 bool search(const string& word) {
23 shared_ptr<TrieNode> node = find(word);
24 return node != nullptr && node->isWord;
25 }
26
27 bool startsWith(const string& prefix) {
28 return find(prefix) != nullptr;
29 }
30
31 private:
32 shared_ptr<TrieNode> root = make_shared<TrieNode>();
33
34 shared_ptr<TrieNode> find(const string& prefix) {
35 shared_ptr<TrieNode> node = root;
36 for (const char c : prefix) {
37 const int i = c - 'a';
38 if (node->children[i] == nullptr)
39 return nullptr;
40 node = node->children[i];
41 }
42 return node;
43 }
44};
Python Solution:
1 2python 3class TrieNode(object): 4 def __init__(self): 5 self.isWord = False 6 self.children = collections.defaultdict(TrieNode) 7 8class Trie(object): 9 10 def __init__(self): 11 self.root = TrieNode() 12 13 def insert(self, word): 14 node = self.root 15 for w in word: 16 node = node.children[w] 17 node.isWord = True 18 19 def search(self, word): 20 node = self.root 21 for w in word: 22 if w not in node.children: 23 return False 24 node = node.children[w] 25 return node.isWord 26 27 def startsWith(self, prefix): 28 node = self.root 29 for w in prefix: 30 if w not in node.children: 31 return False 32 node = node.children[w] 33 return True
Java Solution:
1
2java
3class Trie {
4 private TrieNode root;
5
6 public Trie() {
7 root = new TrieNode();
8 }
9
10 public void insert(String word) {
11 TrieNode node = root;
12 for (int i = 0; i < word.length(); i++) {
13 char currentChar = word.charAt(i);
14 if (!node.containsKey(currentChar)) {
15 node.put(currentChar, new TrieNode());
16 }
17 node = node.get(currentChar);
18 }
19 node.setEnd();
20 }
21
22 private TrieNode searchPrefix(String word) {
23 TrieNode node = root;
24 for (int i = 0; i < word.length(); i++) {
25 char curLetter = word.charAt(i);
26 if (node.containsKey(curLetter)) {
27 node = node.get(curLetter);
28 } else {
29 return null;
30 }
31 }
32 return node;
33 }
34
35 public boolean search(String word) {
36 TrieNode node = searchPrefix(word);
37 return node != null && node.isEnd();
38 }
39
40 public boolean startsWith(String prefix) {
41 TrieNode node = searchPrefix(prefix);
42 return node != null;
43 }
44}
JavaScript Solution:
1 2javascript 3var TrieNode = function(){ 4 this.END = false; 5 this.children = {}; 6}; 7var Trie = function() { 8 this.root = new TrieNode(); 9}; 10 11Trie.prototype.insert = function(word) { 12 let node = this.root; 13 for(let i = 0;i < word.length;i++){ 14 if(node.children[word[i]] == undefined){ 15 node.children[word[i]] = new TrieNode(); 16 } 17 node = node.children[word[i]]; 18 } 19 node.END = true; 20}; 21 22Trie.prototype.search = function(word) { 23 let node = this.root; 24 for(let i = 0;i < word.length;i++){ 25 if(node.children[word[i]] == undefined){ 26 return false; 27 } 28 node = node.children[word[i]]; 29 } 30 return node.END; 31}; 32 33Trie.prototype.startsWith = function(prefix) { 34 let node = this.root; 35 for(let i = 0;i < prefix.length;i++){ 36 if(node.children[prefix[i]] == undefined){ 37 return false; 38 } 39 node = node.children[prefix[i]]; 40 } 41 return true; 42};
C# Solution:
1
2csharp
3public class TrieNode {
4 public Dictionary<char, TrieNode> children = new Dictionary<char, TrieNode>();
5 public bool word = false;
6
7 public void Insert(char[] arr, int index) {
8 if (index == arr.Length) {
9 word = true;
10 return;
11 }
12 var c = arr[index];
13 if (!children.ContainsKey(c)) children[c] = new TrieNode();
14 children[c].Insert(arr, index + 1);
15 }
16
17 public bool Search(char[] arr, int index) {
18 if (index == arr.Length) return word;
19 var c = arr[index];
20 if (!children.ContainsKey(c)) return false;
21 return children[c].Search(arr, index + 1);
22 }
23
24 public bool StartsWith(char[] arr, int index) {
25 if (index == arr.Length) return true;
26 var c = arr[index];
27 if (!children.ContainsKey(c)) return false;
28 return children[c].StartsWith(arr, index + 1);
29 }
30}
31
32public class Trie {
33 TrieNode root;
34
35 public Trie() {
36 root = new TrieNode();
37 }
38
39 public void Insert(string word) {
40 root.Insert(word.ToCharArray(), 0);
41 }
42
43 public bool Search(string word) {
44 return root.Search(word.ToCharArray(), 0);
45 }
46
47 public bool StartsWith(string prefix) {
48 return root.StartsWith(prefix.ToCharArray(), 0);
49 }
50}
That's all for the implementation of Trie data structure and its three most common operations: insert
, search
, and startsWith
in multiple programming languages. It's important to note that the complexity of these operations in a Trie does not depend on the number of keys, but rather on the length of the key. This makes Trie data structure particularly efficient in scenarios where we are dealing with massive sets of strings. For instance, Tries are used widely in real-world applications such as Autocomplete feature in IDEs or web browsers, spell checkers, IP routing (Longest Prefix Matching) and more.
However, one disadvantage of using Trie compared to hash table is that Trie can be more space consuming when storing fewer keys with large associated data. Also, in terms of numeric keys, tries are comparable to binary search trees whereas hash tables which are usually more useful.
Like any other data structure, Tries have their use-cases where they outshine other data structures, and it's crucial for us as software engineers to be aware of where to employ them best. Overall, having a good grasp of Trie can be a good addition to your data structure and algorithm skill set.
Got a question?ย Ask the Teaching Assistantย anything you don't understand.
Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.