Substring of Concatenated Word Permutations
Problem
Given a string s and a list of equal-length words, return every starting index where some permutation of the words appears as a substring back-to-back. Since every word has the same length L, you can step the window in chunks of L instead of one character at a time. For each of the L possible alignment offsets, slide a window across word slots and keep a multiset of "words seen" matching the multiset of "words needed."
Input
s = "barfoothefoobarman", words = ["foo","bar"]Output
[0, 9]"barfoo" at 0 and "foobar" at 9 are both permutations of {foo, bar}.
from collections import Counter
def find_substring(s, words):
k, L = len(words), len(words[0])
need = Counter(words)
result = []
for off in range(L):
l = off
have = Counter()
count = 0
r = off
while r + L <= len(s):
word = s[r:r + L]
if word not in need:
have.clear(); count = 0; l = r + L
else:
have[word] += 1
count += 1
while have[word] > need[word]:
drop = s[l:l + L]
have[drop] -= 1
l += L; count -= 1
if count == k:
result.append(l)
r += L
return result
function findSubstring(s, words) {
const k = words.length, L = words[0].length;
const need = new Map();
for (const w of words) need.set(w, (need.get(w) || 0) + 1);
const result = [];
for (let off = 0; off < L; off++) {
let l = off, count = 0;
const have = new Map();
for (let r = off; r + L <= s.length; r += L) {
const word = s.substring(r, r + L);
if (!need.has(word)) {
have.clear(); count = 0; l = r + L;
continue;
}
have.set(word, (have.get(word) || 0) + 1);
count++;
while (have.get(word) > need.get(word)) {
const drop = s.substring(l, l + L);
have.set(drop, have.get(drop) - 1);
l += L; count--;
}
if (count === k) result.push(l);
}
}
return result;
}
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
int k = words.length, L = words[0].length();
Map<String, Integer> need = new HashMap<>();
for (String w : words) need.merge(w, 1, Integer::sum);
List<Integer> result = new ArrayList<>();
for (int off = 0; off < L; off++) {
int l = off, count = 0;
Map<String, Integer> have = new HashMap<>();
for (int r = off; r + L <= s.length(); r += L) {
String word = s.substring(r, r + L);
if (!need.containsKey(word)) {
have.clear(); count = 0; l = r + L;
continue;
}
have.merge(word, 1, Integer::sum);
count++;
while (have.get(word) > need.get(word)) {
String drop = s.substring(l, l + L);
have.merge(drop, -1, Integer::sum);
l += L; count--;
}
if (count == k) result.add(l);
}
}
return result;
}
}
vector<int> findSubstring(string s, vector<string>& words) {
int k = words.size(), L = words[0].size();
unordered_map<string, int> need;
for (auto& w : words) need[w]++;
vector<int> result;
for (int off = 0; off < L; off++) {
int l = off, count = 0;
unordered_map<string, int> have;
for (int r = off; r + L <= (int)s.size(); r += L) {
string word = s.substr(r, L);
if (!need.count(word)) {
have.clear(); count = 0; l = r + L;
continue;
}
have[word]++; count++;
while (have[word] > need[word]) {
string drop = s.substr(l, L);
have[drop]--;
l += L; count--;
}
if (count == k) result.push_back(l);
}
}
return result;
}