Skip to content

Commit 9ffbcfb

Browse files
authored
Create README.md
1 parent 62404d9 commit 9ffbcfb

File tree

1 file changed

+306
-0
lines changed
  • 21 - Trie Data Structure Problems/04 - Phone Directory

1 file changed

+306
-0
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
<h1 align='center'>Phone - Directory</h1>
2+
3+
## Problem Statement
4+
5+
**Problem URL :** [Phone Directory](https://www.geeksforgeeks.org/problems/phone-directory4628/1)
6+
7+
![image](https://github.com/user-attachments/assets/f2f60d5c-34b1-49f6-a0a7-5ad9746ec8f9)
8+
![image](https://github.com/user-attachments/assets/4ba5724e-ffbd-4afa-b1c7-c90d2b49832d)
9+
![image](https://github.com/user-attachments/assets/6c1e9517-3eed-472a-85de-82a335c1c23b)
10+
11+
## Problem Explanation
12+
The problem at hand is about implementing a **phone directory system** where we are given a list of contacts and a query string. For each prefix of the query string, we need to return a list of contacts that start with that prefix.
13+
14+
#### Problem Breakdown with Example:
15+
16+
- **Input:**
17+
- `contacts[] = ["GEEKS", "FOR", "GEE", "SKEE", "GEEKSFORGEEKS"]`
18+
- `query = "GEE"`
19+
20+
- **Output:**
21+
- `GEE -> ["GEEKS", "GEE", "GEEKSFORGEEKS"]`
22+
- `GEEKS -> ["GEEKS", "GEEKSFORGEEKS"]`
23+
- `GEEKSF -> ["GEEKSFORGEEKS"]`
24+
25+
#### Approach:
26+
To solve the problem, we can use a **Trie (Prefix Tree)** to store the contacts and quickly search for all contacts that share a given prefix.
27+
28+
1. **Trie (Prefix Tree)**:
29+
- The Trie allows us to efficiently search for all words that start with a given prefix. Each node in the Trie represents a character, and the edges represent possible subsequent characters in the word. This helps in optimizing the search process as we can retrieve all words that start with a certain prefix in **O(P)** time, where **P** is the length of the prefix.
30+
31+
2. **Inserting words into the Trie**: For each contact, we insert it character by character into the Trie. Each node will store pointers to its child nodes, and when we reach the end of a word, we mark the node as terminal.
32+
33+
3. **Searching for suggestions**: For each character in the query, we navigate the Trie to find the relevant nodes that match the prefix. From the current node, we perform a **depth-first search (DFS)** to gather all contacts that start with the prefix.
34+
35+
## Problem Solution
36+
```cpp
37+
class TrieNode {
38+
public:
39+
char data;
40+
TrieNode* children[26];
41+
bool isTerminal;
42+
43+
TrieNode(char data){
44+
this -> data = data;
45+
for(int i = 0; i < 26; i++) children[i] = NULL;
46+
isTerminal = false;
47+
}
48+
};
49+
50+
class Trie {
51+
public:
52+
TrieNode* root;
53+
54+
Trie(char ch) {
55+
root = new TrieNode(ch);
56+
}
57+
58+
void insertUtil(TrieNode* root, string word, int index){
59+
if(index == word.size()){
60+
root -> isTerminal = true;
61+
return;
62+
}
63+
64+
int charIndex = word[index] - 'a';
65+
if(root -> children[charIndex] == NULL) root -> children[charIndex] = new TrieNode(word[index]);
66+
67+
insertUtil(root -> children[charIndex], word, index+1);
68+
}
69+
70+
void insertWord(string word){
71+
insertUtil(root, word, 0);
72+
}
73+
74+
75+
76+
void printSuggestions(TrieNode* curr, vector<string>& temp, string prefix){
77+
if(curr -> isTerminal){
78+
temp.push_back(prefix);
79+
}
80+
81+
for(char ch = 'a'; ch <= 'z'; ch++){
82+
TrieNode* next = curr -> children[ch - 'a'];
83+
if(next != NULL){
84+
prefix.push_back(ch);
85+
printSuggestions(next, temp, prefix);
86+
prefix.pop_back();
87+
}
88+
}
89+
}
90+
91+
vector<vector<string>> getSuggestions(string str){
92+
TrieNode* prev = root;
93+
vector<vector<string>> output;
94+
95+
string prefix = "";
96+
97+
for(int i = 0; i < str.length(); i++){
98+
char lastChar = str[i];
99+
prefix.push_back(lastChar);
100+
101+
TrieNode* curr = prev -> children[lastChar - 'a'];
102+
if(curr == NULL) {
103+
for(int j = i; j < str.length(); j++) output.push_back({"0"});
104+
break;
105+
}
106+
107+
vector<string> temp;
108+
printSuggestions(curr, temp, prefix);
109+
110+
if(temp.empty()) output.push_back({"0"});
111+
else{
112+
sort(temp.begin(), temp.end());
113+
output.push_back(temp);
114+
}
115+
116+
prev = curr;
117+
}
118+
119+
return output;
120+
}
121+
122+
};
123+
124+
class Solution {
125+
public:
126+
vector<vector<string>> displayContacts(int n, string contact[], string s) {
127+
Trie* t = new Trie('\0');
128+
129+
for(int i = 0; i < n; i++){
130+
string str = contact[i];
131+
t -> insertWord(str);
132+
}
133+
134+
return t -> getSuggestions(s);
135+
}
136+
};
137+
```
138+
139+
## Problem Solution Explanation
140+
Below is a detailed **line-by-line explanation** of the given code.
141+
142+
#### TrieNode Class:
143+
```cpp
144+
class TrieNode {
145+
public:
146+
char data; // Stores the character at this node.
147+
TrieNode* children[26]; // Array to store child nodes (one for each letter of the alphabet).
148+
bool isTerminal; // Indicates whether this node is the end of a word.
149+
150+
TrieNode(char data) {
151+
this -> data = data; // Initialize the character at this node.
152+
for(int i = 0; i < 26; i++) children[i] = NULL; // Initializing all children to NULL.
153+
isTerminal = false; // Mark as non-terminal initially.
154+
}
155+
};
156+
```
157+
- **`TrieNode` class**:
158+
- The `TrieNode` class represents a single node in the Trie.
159+
- **`data`**: Stores the character for that node.
160+
- **`children[26]`**: An array of size 26, where each index represents a letter of the alphabet. For example, `children[0]` is for 'a', `children[1]` is for 'b', and so on.
161+
- **`isTerminal`**: A boolean variable that indicates whether this node is the end of a word. For example, if the word ends at this node, `isTerminal` is `true`.
162+
163+
#### Trie Class:
164+
```cpp
165+
class Trie {
166+
public:
167+
TrieNode* root;
168+
169+
Trie(char ch) {
170+
root = new TrieNode(ch); // Initialize the Trie with a root node.
171+
}
172+
```
173+
- **`Trie` class**:
174+
- **`root`**: This is the root node of the Trie.
175+
- **`Trie(char ch)`**: Constructor initializes the Trie with a root node having an arbitrary character `ch`.
176+
177+
##### Insert Word Function:
178+
```cpp
179+
void insertUtil(TrieNode* root, string word, int index) {
180+
if(index == word.size()) {
181+
root -> isTerminal = true; // Mark end of word when we reach the last character.
182+
return;
183+
}
184+
185+
int charIndex = word[index] - 'a'; // Calculate the index of the current character.
186+
if(root -> children[charIndex] == NULL) // If no node exists for this character, create one.
187+
root -> children[charIndex] = new TrieNode(word[index]);
188+
189+
insertUtil(root -> children[charIndex], word, index+1); // Recurse to next character.
190+
}
191+
```
192+
- **`insertUtil` function**:
193+
- This function recursively inserts the characters of a word into the Trie.
194+
- **Base Case**: If `index == word.size()`, it means we have inserted all characters of the word. So, we mark this node as `isTerminal = true` to signify that the word ends here.
195+
- **`charIndex`**: This calculates the index of the character in the `children` array (0 for 'a', 1 for 'b', ..., 25 for 'z').
196+
- **Checking and Creating Children**: If the child node for the current character doesn’t exist, we create a new `TrieNode`.
197+
- **Recursive Call**: The function then recursively calls itself to insert the next character in the word.
198+
199+
```cpp
200+
void insertWord(string word) {
201+
insertUtil(root, word, 0); // Start inserting the word from the root node.
202+
}
203+
```
204+
- **`insertWord` function**: This is the public function that starts the insertion process. It calls `insertUtil` starting from the root node.
205+
206+
##### Print Suggestions Function:
207+
```cpp
208+
void printSuggestions(TrieNode* curr, vector<string>& temp, string prefix) {
209+
if(curr -> isTerminal) {
210+
temp.push_back(prefix); // If a terminal node is reached, add the prefix to suggestions.
211+
}
212+
213+
for(char ch = 'a'; ch <= 'z'; ch++) {
214+
TrieNode* next = curr -> children[ch - 'a'];
215+
if(next != NULL) {
216+
prefix.push_back(ch); // Add the character to the prefix.
217+
printSuggestions(next, temp, prefix); // Recurse to find more suggestions.
218+
prefix.pop_back(); // Backtrack to try other children.
219+
}
220+
}
221+
}
222+
```
223+
- **`printSuggestions` function**:
224+
- This function is used to generate the list of all possible words from a given node (starting from a specific character).
225+
- **Terminal Node Check**: If the current node (`curr`) is terminal, we add the current prefix to the `temp` vector.
226+
- **Recursive DFS**: We loop through all characters (`a` to `z`) and recursively explore the children. If the child node exists, we append the character to the `prefix` and continue exploring deeper nodes.
227+
228+
##### Get Suggestions Function:
229+
```cpp
230+
vector<vector<string>> getSuggestions(string str) {
231+
TrieNode* prev = root;
232+
vector<vector<string>> output;
233+
string prefix = "";
234+
235+
for(int i = 0; i < str.length(); i++) {
236+
char lastChar = str[i];
237+
prefix.push_back(lastChar);
238+
TrieNode* curr = prev -> children[lastChar - 'a'];
239+
240+
if(curr == NULL) {
241+
for(int j = i; j < str.length(); j++) output.push_back({"0"}); // If no suggestions, add "0".
242+
break;
243+
}
244+
245+
vector<string> temp;
246+
printSuggestions(curr, temp, prefix); // Get all suggestions for the current prefix.
247+
248+
if(temp.empty()) output.push_back({"0"}); // If no suggestions, add "0".
249+
else {
250+
sort(temp.begin(), temp.end()); // Sort suggestions lexicographically.
251+
output.push_back(temp);
252+
}
253+
254+
prev = curr; // Move to the next character in the query.
255+
}
256+
257+
return output; // Return all suggestions.
258+
}
259+
```
260+
- **`getSuggestions` function**:
261+
- This function finds all possible suggestions for each prefix of the query string.
262+
- **Loop through the query string**: We loop through each character in the query string.
263+
- **Prefix Update**: For each character in the query string, we append it to the `prefix` and try to find the corresponding child node in the Trie.
264+
- **If No Match**: If no child node is found (i.e., the prefix does not exist in the Trie), we add `{"0"}` to the output, indicating no suggestions.
265+
- **Get Suggestions**: If the node for the current prefix exists, we call `printSuggestions` to gather all suggestions starting from that node.
266+
- **Sort and Add Suggestions**: If there are any suggestions, we sort them lexicographically before adding them to the output. If there are no suggestions, we add `{"0"}` to indicate that there are no suggestions for the current prefix.
267+
- **Move to the Next Character**: After processing the current prefix, we move on to the next character in the query.
268+
269+
#### Solution Class:
270+
```cpp
271+
class Solution {
272+
public:
273+
vector<vector<string>> displayContacts(int n, string contact[], string s) {
274+
Trie* t = new Trie('\0'); // Create a new Trie instance.
275+
276+
for(int i = 0; i < n; i++) {
277+
string str = contact[i];
278+
t -> insertWord(str); // Insert each contact into the Trie.
279+
}
280+
281+
return t -> getSuggestions(s); // Get suggestions for the query string.
282+
}
283+
};
284+
```
285+
- **`displayContacts` function**:
286+
- **Create Trie**: A new `Trie` object is created.
287+
- **Insert Contacts**: We loop through the list of contacts and insert each one into the Trie using the `insertWord` function.
288+
- **Get Suggestions**: After inserting all the contacts, we call `getSuggestions` to retrieve the suggestions for the query string.
289+
290+
### Time and Space Complexity
291+
292+
- **Time Complexity**:
293+
- **Insertion**: Each word is inserted character by character. The time complexity for inserting a word is `O(L)` where `L` is the length of the word. Inserting `n` words would take `O(n * L)` time.
294+
- **Query**: For each character in the query, we look up a node and recursively gather suggestions. For each prefix, we perform a DFS to gather all suggestions. The complexity for each query is `O(m * L * 26)` where `m` is the length of the query string.
295+
296+
- **Space Complexity**:
297+
- **Trie Storage**: The Trie requires space to store all nodes for each word. The space complexity is `O(n * L)` where `n` is the number of words and `L` is the average length of the words.
298+
299+
### Conclusion:
300+
- The Trie is an efficient data structure for this problem as it allows quick prefix-based searches and word insertions.
301+
- The approach leverages recursion to handle both insertion and prefix suggestion generation.
302+
303+
### Step 5: Additional Recommendations
304+
305+
- **Efficient Search**: The Trie is highly efficient for prefix-based searches, but keep in mind that it consumes more memory compared to other methods.
306+
- **Edge Cases**: Make sure to handle cases where no matches are found by returning `0` as shown in the solution.

0 commit comments

Comments
 (0)