|
| 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 | + |
| 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