|
| 1 | +--- |
| 2 | +id: Substring with Concatenation of All Words |
| 3 | +title: Substring with Concatenation of All Words |
| 4 | +sidebar_label: 0030-Substring-with-Concatenation-of-All-Words |
| 5 | +tags: |
| 6 | + - Hard |
| 7 | + - String |
| 8 | + - Hash Table |
| 9 | + - Two Pointers |
| 10 | +description: A concatenated string is a string that exactly contains all the strings of any permutation of `words` concatenated. |
| 11 | +--- |
| 12 | + |
| 13 | +### Problem Description: |
| 14 | +| Problem Statement | Solution Link | LeetCode Profile | |
| 15 | +| :------------------------------------------------------ | :------------------------------------------------------------------------- | :------------------------------------------------------ | |
| 16 | +| [Substring with Concatenation of All Words](https://leetcode.com/problems/0030-Substring-with-Concatenation-of-All-Words/description/) | [Substring with Concatenation of All Words Solution on LeetCode](https://leetcode.com/problems/0030-Substring-with-Concatenation-of-All-Words/solutions/) | [Nikita Saini](https://leetcode.com/u/Saini_Nikita/) | |
| 17 | + |
| 18 | +## Problem Description |
| 19 | +You are given a string `s` and an array of strings `words`. All the strings in `words` are of the same length. |
| 20 | + |
| 21 | +### Examples |
| 22 | + |
| 23 | +**Example 1:** |
| 24 | + |
| 25 | +- **Input:** `s = "barfoothefoobarman"`, `words = ["foo","bar"]` |
| 26 | +- **Output:** `[0,9]` |
| 27 | + |
| 28 | +- **Explanation:** |
| 29 | +- The substring starting at 0 is "barfoo". It is the concatenation of `["bar","foo"]` which is a permutation of `words`. |
| 30 | +- The substring starting at 9 is "foobar". It is the concatenation of `["foo","bar"]` which is a permutation of `words`. |
| 31 | + |
| 32 | +**Example 2:** |
| 33 | + |
| 34 | +- **Input:** `s = "wordgoodgoodgoodbestword"`, `words = ["word","good","best","word"]` |
| 35 | +- **Output:** `[]` |
| 36 | + |
| 37 | +- **Explanation:** |
| 38 | +- There is no concatenated substring. |
| 39 | + |
| 40 | +**Example 3:** |
| 41 | + |
| 42 | +- **Input:** `s = "barfoofoobarthefoobarman"`, `words = ["bar","foo","the"]` |
| 43 | +- **Output:** `[6,9,12]` |
| 44 | + |
| 45 | +- **Explanation:** |
| 46 | +- The substring starting at 6 is "foobarthe". It is the concatenation of `["foo","bar","the"]`. |
| 47 | +- The substring starting at 9 is "barthefoo". It is the concatenation of `["bar","the","foo"]`. |
| 48 | +- The substring starting at 12 is "thefoobar". It is the concatenation of `["the","foo","bar"]`. |
| 49 | + |
| 50 | +### Constraints |
| 51 | + |
| 52 | +- 1 <= s.length <= 10^4 |
| 53 | +- 1 <= words.length <= 5000 |
| 54 | +- 1 <= words[i].length <= 30 |
| 55 | +- s and words[i] consist of lowercase English letters. |
| 56 | + |
| 57 | +## Approach |
| 58 | +We will use a sliding window approach to solve this problem. |
| 59 | +1. First, we calculate the length of each word in the `words` array and the total length of the concatenated substring. |
| 60 | +2. We create a hashmap to store the count of each word in `words`. |
| 61 | +3. We iterate over each possible starting index in the string `s` using a sliding window of size equal to the total length of the concatenated substring. |
| 62 | +4. For each substring, we check if it can be formed by concatenating all the words in `words`. |
| 63 | +5. To check the validity of each window, we use another hashmap to count occurrences of words in the current window and compare it to the word count map. |
| 64 | +6. If the current window is valid, we add its starting index to the result list. |
| 65 | + |
| 66 | +## Solution in Java |
| 67 | +```java |
| 68 | +import java.util.*; |
| 69 | + |
| 70 | +public class Solution { |
| 71 | + public List<Integer> findSubstring(String s, String[] words) { |
| 72 | + List<Integer> result = new ArrayList<>(); |
| 73 | + if (s == null || s.length() == 0 || words == null || words.length == 0) { |
| 74 | + return result; |
| 75 | + } |
| 76 | + |
| 77 | + int wordLength = words[0].length(); |
| 78 | + int wordCount = words.length; |
| 79 | + int totalLength = wordLength * wordCount; |
| 80 | + |
| 81 | + // Create a map to count the words |
| 82 | + Map<String, Integer> wordMap = new HashMap<>(); |
| 83 | + for (String word : words) { |
| 84 | + wordMap.put(word, wordMap.getOrDefault(word, 0) + 1); |
| 85 | + } |
| 86 | + |
| 87 | + // Slide the window over the string `s` |
| 88 | + for (int i = 0; i <= s.length() - totalLength; i++) { |
| 89 | + String currentSubstring = s.substring(i, i + totalLength); |
| 90 | + if (isValid(currentSubstring, wordMap, wordLength, wordCount)) { |
| 91 | + result.add(i); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + return result; |
| 96 | + } |
| 97 | + |
| 98 | + private boolean isValid(String substring, Map<String, Integer> wordMap, int wordLength, int wordCount) { |
| 99 | + Map<String, Integer> seen = new HashMap<>(); |
| 100 | + for (int j = 0; j < wordCount; j++) { |
| 101 | + int wordStartIndex = j * wordLength; |
| 102 | + String word = substring.substring(wordStartIndex, wordStartIndex + wordLength); |
| 103 | + if (!wordMap.containsKey(word)) { |
| 104 | + return false; |
| 105 | + } |
| 106 | + seen.put(word, seen.getOrDefault(word, 0) + 1); |
| 107 | + if (seen.get(word) > wordMap.get(word)) { |
| 108 | + return false; |
| 109 | + } |
| 110 | + } |
| 111 | + return true; |
| 112 | + } |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +### Solution in Python |
| 117 | +```python |
| 118 | +class Solution: |
| 119 | + def findSubstring(self, s: str, words: List[str]) -> List[int]: |
| 120 | + result = [] |
| 121 | + if not s or not words: |
| 122 | + return result |
| 123 | + |
| 124 | + word_length = len(words[0]) |
| 125 | + word_count = len(words) |
| 126 | + total_length = word_length * word_count |
| 127 | + |
| 128 | + word_map = {} |
| 129 | + for word in words: |
| 130 | + word_map[word] = word_map.get(word, 0) + 1 |
| 131 | + |
| 132 | + for i in range(len(s) - total_length + 1): |
| 133 | + current_substring = s[i:i + total_length] |
| 134 | + if self.is_valid(current_substring, word_map, word_length, word_count): |
| 135 | + result.append(i) |
| 136 | + |
| 137 | + return result |
| 138 | + |
| 139 | + def is_valid(self, substring, word_map, word_length, word_count): |
| 140 | + seen = {} |
| 141 | + for j in range(word_count): |
| 142 | + word_start_index = j * word_length |
| 143 | + word = substring[word_start_index:word_start_index + word_length] |
| 144 | + if word not in word_map: |
| 145 | + return False |
| 146 | + seen[word] = seen.get(word, 0) + 1 |
| 147 | + if seen[word] > word_map[word]: |
| 148 | + return False |
| 149 | + return True |
| 150 | +``` |
| 151 | + |
| 152 | +### Solution in CPP |
| 153 | +```cpp |
| 154 | +#include <iostream> |
| 155 | +#include <vector> |
| 156 | +#include <string> |
| 157 | +#include <unordered_map> |
| 158 | + |
| 159 | +using namespace std; |
| 160 | + |
| 161 | +class Solution { |
| 162 | +public: |
| 163 | + vector<int> findSubstring(string s, vector<string>& words) { |
| 164 | + vector<int> result; |
| 165 | + if (s.empty() || words.empty()) { |
| 166 | + return result; |
| 167 | + } |
| 168 | + |
| 169 | + int wordLength = words[0].length(); |
| 170 | + int wordCount = words.size(); |
| 171 | + int totalLength = wordLength * wordCount; |
| 172 | + |
| 173 | + unordered_map<string, int> wordMap; |
| 174 | + for (const string& word : words) { |
| 175 | + wordMap[word]++; |
| 176 | + } |
| 177 | + |
| 178 | + for (int i = 0; i <= s.length() - totalLength; i++) { |
| 179 | + string currentSubstring = s.substr(i, totalLength); |
| 180 | + if (isValid(currentSubstring, wordMap, wordLength, wordCount)) { |
| 181 | + result.push_back(i); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + return result; |
| 186 | + } |
| 187 | + |
| 188 | + bool isValid(string substring, unordered_map<string, int>& wordMap, int wordLength, int wordCount) { |
| 189 | + unordered_map<string, int> seen; |
| 190 | + for (int j = 0; j < wordCount; j++) { |
| 191 | + string word = substring.substr(j * wordLength, wordLength); |
| 192 | + if (wordMap.find(word) == wordMap.end()) { |
| 193 | + return false; |
| 194 | + } |
| 195 | + seen[word]++; |
| 196 | + if (seen[word] > wordMap[word]) { |
| 197 | + return false; |
| 198 | + } |
| 199 | + } |
| 200 | + return true; |
| 201 | + } |
| 202 | +}; |
| 203 | + |
| 204 | +int main() { |
| 205 | + Solution solution; |
| 206 | + string s = "barfoothefoobarman"; |
| 207 | + vector<string> words = {"foo", "bar"}; |
| 208 | + vector<int> result = solution.findSubstring(s, words); |
| 209 | + for (int idx : result) { |
| 210 | + cout << idx << " "; |
| 211 | + } |
| 212 | + cout << endl; |
| 213 | + return 0; |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +### Solution in C |
| 218 | +```c |
| 219 | +#include <stdio.h> |
| 220 | +#include <stdlib.h> |
| 221 | +#include <string.h> |
| 222 | + |
| 223 | +#define MAX_WORD_LENGTH 30 |
| 224 | + |
| 225 | +int isValid(char* substring, char** words, int* wordMap, int wordLength, int wordCount) { |
| 226 | + int seen[5000] = {0}; |
| 227 | + for (int j = 0; j < wordCount; j++) { |
| 228 | + char* word = substring + j * wordLength; |
| 229 | + int index = -1; |
| 230 | + for (int k = 0; k < wordCount; k++) { |
| 231 | + if (strcmp(word, words[k]) == 0) { |
| 232 | + index = k; |
| 233 | + break; |
| 234 | + } |
| 235 | + } |
| 236 | + if (index == -1 || wordMap[index] == 0) { |
| 237 | + return 0; |
| 238 | + } |
| 239 | + seen[index]++; |
| 240 | + if (seen[index] > wordMap[index]) { |
| 241 | + return 0; |
| 242 | + } |
| 243 | + } |
| 244 | + return 1; |
| 245 | +} |
| 246 | + |
| 247 | +int* findSubstring(char* s, char** words, int wordsSize, int* returnSize) { |
| 248 | + *returnSize = 0; |
| 249 | + if (!s || !words || wordsSize == 0) { |
| 250 | + return NULL; |
| 251 | + } |
| 252 | + |
| 253 | + int wordLength = strlen(words[0]); |
| 254 | + int wordCount = wordsSize; |
| 255 | + int totalLength = wordLength * wordCount; |
| 256 | + |
| 257 | + int* result = (int*)malloc(sizeof(int) * 5000); |
| 258 | + if (!result) { |
| 259 | + return NULL; |
| 260 | + } |
| 261 | + |
| 262 | + int wordMap[5000] = {0}; |
| 263 | + for (int i = 0; i < wordCount; i++) { |
| 264 | + int found = 0; |
| 265 | + for (int j = 0; j < i; j++) { |
| 266 | + if (strcmp(words[i], words[j]) == 0) { |
| 267 | + found = 1; |
| 268 | + wordMap[j]++; |
| 269 | + break; |
| 270 | + } |
| 271 | + } |
| 272 | + if (!found) { |
| 273 | + wordMap[i]++; |
| 274 | + } |
| 275 | + } |
| 276 | + |
| 277 | + for (int i = 0; i <= strlen(s) - totalLength; i++) { |
| 278 | + char currentSubstring[totalLength + 1]; |
| 279 | + strncpy(currentSubstring, s + i, totalLength); |
| 280 | + currentSubstring[totalLength] = '\0'; |
| 281 | + if (isValid(currentSubstring, words, wordMap, wordLength, wordCount)) { |
| 282 | + result[(*returnSize)++] = i; |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + return result; |
| 287 | +} |
| 288 | + |
| 289 | +int main() { |
| 290 | + char* s = "barfoothefoobarman"; |
| 291 | + char* words[2] = {"foo", "bar"}; |
| 292 | + int wordsSize = 2; |
| 293 | + int returnSize; |
| 294 | + int* result = findSubstring(s, words, wordsSize, &returnSize); |
| 295 | + for (int i = 0; i < returnSize; i++) { |
| 296 | + printf("%d ", result[i]); |
| 297 | + } |
| 298 | + printf("\n"); |
| 299 | + free(result); |
| 300 | + return 0; |
| 301 | +} |
| 302 | +``` |
| 303 | +
|
| 304 | +### Solution in JavaScript |
| 305 | +```js |
| 306 | +/** |
| 307 | + * @param {string} s |
| 308 | + * @param {string[]} words |
| 309 | + * @return {number[]} |
| 310 | + */ |
| 311 | +var findSubstring = function(s, words) { |
| 312 | + const result = []; |
| 313 | + if (!s || !words || words.length === 0) { |
| 314 | + return result; |
| 315 | + } |
| 316 | +
|
| 317 | + const wordLength = words[0].length; |
| 318 | + const wordCount = words.length; |
| 319 | + const totalLength = wordLength * wordCount; |
| 320 | +
|
| 321 | + const wordMap = {}; |
| 322 | + words.forEach(word => { |
| 323 | + if (wordMap[word]) { |
| 324 | + wordMap[word]++; |
| 325 | + } else { |
| 326 | + wordMap[word] = 1; |
| 327 | + } |
| 328 | + }); |
| 329 | +
|
| 330 | + for (let i = 0; i <= s.length - totalLength; i++) { |
| 331 | + const currentSubstring = s.substring(i, i + totalLength); |
| 332 | + if (isValid(currentSubstring, wordMap, wordLength, wordCount)) { |
| 333 | + result.push(i); |
| 334 | + } |
| 335 | + } |
| 336 | +
|
| 337 | + return result; |
| 338 | +}; |
| 339 | +
|
| 340 | +function isValid(substring, wordMap, wordLength, wordCount) { |
| 341 | + const seen = {}; |
| 342 | + for (let j = 0; j < wordCount; j++) { |
| 343 | + const word = substring.substr(j * wordLength, wordLength); |
| 344 | + if (!wordMap[word]) { |
| 345 | + return false; |
| 346 | + } |
| 347 | + if (!seen[word]) { |
| 348 | + seen[word] = 1; |
| 349 | + } else { |
| 350 | + seen[word]++; |
| 351 | + } |
| 352 | + if (seen[word] > wordMap[word]) { |
| 353 | + return false; |
| 354 | + } |
| 355 | + } |
| 356 | + return true; |
| 357 | +} |
| 358 | +
|
| 359 | +// Example usage: |
| 360 | +const s = "barfoothefoobarman"; |
| 361 | +const words = ["foo", "bar"]; |
| 362 | +console.log(findSubstring(s, words)); // Output: [0, 9] |
| 363 | +``` |
| 364 | + |
| 365 | +### Step by Step Algorithm |
| 366 | +1. Initialize an empty list `result` to store the starting indices of all the concatenated substrings. |
| 367 | +2. Check for base cases: if `s` or `words` is empty, return the empty list `result`. |
| 368 | +3. Calculate the length of each word in the `words` array and the total length of the concatenated substring. |
| 369 | +4. Create a hashmap `word_map` to store the count of each word in `words`. |
| 370 | +5. Iterate over each possible starting index in the string `s` using a sliding window of size equal to the total length of the concatenated substring. |
| 371 | +6. For each substring, check if it can be formed by concatenating all the words in `words`. |
| 372 | +7. To check the validity of each window, use another hashmap `seen` to count occurrences of words in the current window and compare it to the `word_map`. |
| 373 | +8. If the current window is valid, add its starting index to the `result` list. |
| 374 | +9. Return the `result` list. |
| 375 | + |
| 376 | +### Conclusion |
| 377 | +This problem can be efficiently solved using a sliding window approach. By using a hashmap to store the count of words and iterating over each possible starting index in the string `s`, we can find all the concatenated substrings in `s` that are formed by concatenating all the strings of any permutation of `words`. The time complexity of this solution is O(NML), where N is the length of the string `s`, M is the number of words, and L is the length of each word. |
0 commit comments