Skip to content

Commit 3087fbb

Browse files
authored
Create README.md
1 parent ea936c9 commit 3087fbb

File tree

1 file changed

+338
-0
lines changed
  • 21 - Trie Data Structure Problems/03 - Longest Common Prefix

1 file changed

+338
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
<h1 align='center'>Longest - Common - Prefix</h1>
2+
3+
## Problem Statement
4+
5+
**Problem URL :** [Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/)
6+
7+
![image](https://github.com/user-attachments/assets/10b79c3e-2c71-457b-9abb-b3ccb0c7298b)
8+
9+
## Problem Explanation
10+
#### Problem:
11+
Given a list of strings, you need to find the **longest common prefix** (LCP) that is common to all the strings in the list.
12+
13+
#### Explanation for Beginners:
14+
The problem asks us to find the longest string that appears at the start of all given strings. For example, if we are given the following list of strings:
15+
- `["flower", "flow", "flight"]`
16+
17+
The longest common prefix is **"fl"** because it is the longest string that is common at the beginning of all the strings in the list.
18+
19+
#### Example 1:
20+
**Input**: `["flower", "flow", "flight"]`
21+
**Output**: `"fl"`
22+
23+
- All the strings start with `"fl"`, so that is the longest common prefix.
24+
25+
#### Example 2:
26+
**Input**: `["dog", "racecar", "car"]`
27+
**Output**: `""` (No common prefix)
28+
29+
- There is no common prefix in any of the strings, so the output is an empty string.
30+
31+
#### Approach:
32+
1. **Iterate through each string in the list**: We need to compare each string to see what part of the string matches with the others.
33+
2. **Use a Trie (Prefix Tree)**: A Trie is a tree-like data structure that is used to store a collection of strings. It’s perfect for this problem because we can easily traverse the Trie to find the common prefix between all strings.
34+
3. **Insert words into the Trie**: As we insert each word into the Trie, we track the common prefix by checking how much of the word shares a path with the other words already inserted.
35+
4. **Find the longest common prefix**: By traversing the Trie from the root, we check how much of the prefix is common to all the words.
36+
37+
## Problem Solution
38+
```cpp
39+
class TrieNode{
40+
public:
41+
char data;
42+
TrieNode* children[26];
43+
int childCount;
44+
bool isTerminal;
45+
46+
TrieNode(int data){
47+
this -> data = data;
48+
for(int i = 0; i < 26; i++) children[i] = NULL;
49+
isTerminal = false;
50+
childCount = 0;
51+
}
52+
};
53+
54+
class Trie{
55+
public:
56+
TrieNode* root;
57+
Trie(char ch){
58+
root = new TrieNode(ch);
59+
}
60+
61+
void insertUtil(TrieNode* root, string& word, int index){
62+
if(index == word.size()){
63+
root -> isTerminal = true;
64+
return;
65+
}
66+
67+
int charIndex = word[index] - 'a';
68+
if(root -> children[charIndex] == NULL){
69+
root -> children[charIndex] = new TrieNode(word[index]);
70+
root -> childCount++;
71+
}
72+
73+
insertUtil(root -> children[charIndex], word, index+1);
74+
}
75+
76+
void insertWord(string& word){
77+
insertUtil(root, word, 0);
78+
}
79+
80+
void lcp(string str, string& ans){
81+
TrieNode* node = root;
82+
for(int i = 0; i < str.length(); i++){
83+
char ch = str[i];
84+
85+
if(node -> childCount == 1) {
86+
ans.push_back(ch);
87+
88+
int index = ch - 'a';
89+
90+
node = node -> children[index];
91+
}else break;
92+
93+
if(node -> isTerminal) break;
94+
}
95+
}
96+
97+
~Trie() {
98+
deleteTrie(root);
99+
}
100+
private:
101+
void deleteTrie(TrieNode* node) {
102+
if (!node) return;
103+
for (int i = 0; i < 26; i++) {
104+
deleteTrie(node->children[i]);
105+
}
106+
delete node;
107+
}
108+
};
109+
class Solution {
110+
public:
111+
string longestCommonPrefix(vector<string>& strs) {
112+
if(strs.empty()) return "";
113+
114+
for(string& str : strs) if(str.empty()) return "";
115+
116+
Trie* t = new Trie('\0');
117+
118+
for(int i = 0; i < strs.size(); i++) t->insertWord(strs[i]);
119+
120+
string first = strs[0];
121+
string ans = "";
122+
123+
t -> lcp(first, ans);
124+
125+
return ans;
126+
}
127+
};
128+
```
129+
130+
## Problem Solution Explanation
131+
132+
Let’s break down the given code line by line.
133+
134+
#### TrieNode Class:
135+
136+
```cpp
137+
class TrieNode {
138+
public:
139+
char data;
140+
TrieNode* children[26];
141+
int childCount;
142+
bool isTerminal;
143+
144+
TrieNode(int data){
145+
this -> data = data;
146+
for(int i = 0; i < 26; i++) children[i] = NULL;
147+
isTerminal = false;
148+
childCount = 0;
149+
}
150+
};
151+
```
152+
153+
- **TrieNode**: This class represents each node in the Trie.
154+
- `data`: Stores the character of the node.
155+
- `children`: An array of pointers to the 26 children (for each letter a to z).
156+
- `childCount`: Keeps track of the number of children nodes (this is used to identify whether we have a common prefix).
157+
- `isTerminal`: A flag that indicates whether this node marks the end of a word.
158+
159+
#### Trie Class:
160+
161+
```cpp
162+
class Trie {
163+
public:
164+
TrieNode* root;
165+
Trie(char ch){
166+
root = new TrieNode(ch);
167+
}
168+
```
169+
170+
- **Trie**: This class represents the Trie data structure itself.
171+
- `root`: Points to the root of the Trie (initially an empty node).
172+
- The constructor initializes the `root`.
173+
174+
#### Insert Function:
175+
176+
```cpp
177+
void insertUtil(TrieNode* root, string& word, int index){
178+
if(index == word.size()){
179+
root -> isTerminal = true;
180+
return;
181+
}
182+
183+
int charIndex = word[index] - 'a';
184+
if(root -> children[charIndex] == NULL){
185+
root -> children[charIndex] = new TrieNode(word[index]);
186+
root -> childCount++;
187+
}
188+
189+
insertUtil(root -> children[charIndex], word, index+1);
190+
}
191+
```
192+
193+
- **insertUtil**: A recursive function to insert a word into the Trie.
194+
- It inserts the word starting from the root, character by character.
195+
- If a node doesn’t exist for the current character, a new node is created.
196+
- If the node is the last character of the word, we mark it as `isTerminal = true`.
197+
198+
#### Insert Word:
199+
200+
```cpp
201+
void insertWord(string& word){
202+
insertUtil(root, word, 0);
203+
}
204+
```
205+
206+
- **insertWord**: This function is used to insert a word starting from the root of the Trie. It calls the `insertUtil` helper function with the root node.
207+
208+
#### Longest Common Prefix (LCP) Function:
209+
210+
```cpp
211+
void lcp(string str, string& ans){
212+
TrieNode* node = root;
213+
for(int i = 0; i < str.length(); i++){
214+
char ch = str[i];
215+
216+
if(node -> childCount == 1) {
217+
ans.push_back(ch);
218+
219+
int index = ch - 'a';
220+
221+
node = node -> children[index];
222+
}else break;
223+
224+
if(node -> isTerminal) break;
225+
}
226+
}
227+
```
228+
229+
- **lcp**: This function finds the longest common prefix.
230+
- It starts from the root of the Trie and processes the input string character by character.
231+
- If a node has only one child (i.e., there’s only one possible path), it is added to the prefix.
232+
- The loop stops when:
233+
- The node has more than one child (i.e., we can’t extend the prefix).
234+
- A node is a terminal node (i.e., it marks the end of a word).
235+
236+
#### Destructor:
237+
238+
```cpp
239+
~Trie() {
240+
deleteTrie(root);
241+
}
242+
```
243+
244+
- The destructor ensures that the memory allocated for the Trie nodes is freed when the Trie object is destroyed.
245+
246+
#### Helper Function to Delete Trie:
247+
248+
```cpp
249+
void deleteTrie(TrieNode* node) {
250+
if (!node) return;
251+
for (int i = 0; i < 26; i++) {
252+
deleteTrie(node->children[i]);
253+
}
254+
delete node;
255+
}
256+
```
257+
258+
- **deleteTrie**: This recursive function deletes all nodes in the Trie to avoid memory leaks.
259+
260+
261+
262+
#### Solution Class:
263+
264+
```cpp
265+
class Solution {
266+
public:
267+
string longestCommonPrefix(vector<string>& strs) {
268+
if(strs.empty()) return "";
269+
270+
for(string& str : strs) if(str.empty()) return "";
271+
272+
Trie* t = new Trie('\0');
273+
274+
for(int i = 0; i < strs.size(); i++) t->insertWord(strs[i]);
275+
276+
string first = strs[0];
277+
string ans = "";
278+
279+
t -> lcp(first, ans);
280+
281+
return ans;
282+
}
283+
};
284+
```
285+
286+
- **longestCommonPrefix**: This is the main function where:
287+
- It checks if the list of strings is empty or contains an empty string (if true, return empty).
288+
- It creates a Trie and inserts each string from the list into the Trie.
289+
- After inserting all the strings, it uses the `lcp` function to find the longest common prefix.
290+
291+
292+
293+
### Step 3: Example Walkthrough
294+
295+
Let's use the following example to understand how the code works:
296+
297+
**Example**:
298+
Input: `["flower", "flow", "flight"]`
299+
300+
1. Insert "flower", "flow", and "flight" into the Trie.
301+
- The Trie will look like this:
302+
```
303+
root -> f -> l -> o -> w -> e -> r
304+
-> f -> l -> i -> g -> h -> t
305+
```
306+
307+
2. Now, find the longest common prefix using the `lcp` function:
308+
- Start with "flower". The first character `'f'` has only one child `'l'`, so add `'f'` to the prefix.
309+
- The next character `'l'` also has only one child `'o'` or `'f'`, so add `'l'` to the prefix.
310+
- The next characters `'o'` and `'w'` diverge, so the longest common prefix stops at "fl".
311+
312+
Output: `"fl"`
313+
314+
315+
316+
### Step 4: Time and Space Complexity
317+
318+
#### Time Complexity:
319+
- **Insert Operation**: Inserting a single word takes O(L) time, where L is the length of the word. For N words, the time complexity will be O(N * L).
320+
- **Longest Common Prefix**: The LCP function checks each character of the first word, so it takes O(L) time, where L is the length of the first word.
321+
322+
Thus, the total time complexity is O(N * L), where N is the number of words and L is the average length of the words.
323+
324+
#### Space Complexity:
325+
- **Trie Storage**: The space complexity depends on the number of nodes in the Trie. In the worst case, if all characters in all words are distinct, the space complexity will be O(N * L).
326+
327+
Thus, the space complexity is O(N * L).
328+
329+
330+
331+
### Step 5: Additional Recommendations
332+
333+
- **Edge Cases**: Consider edge cases like:
334+
- An empty list of strings.
335+
- A list with strings that have no common prefix.
336+
- A list where all strings are the same.
337+
338+
- **Optimization**: The given solution is efficient for most use cases. However, for extremely large inputs, you might want to optimize memory usage by compressing the Trie (using techniques like path compression).

0 commit comments

Comments
 (0)