01
Problem Statement
Given a string s and an array of equal-length words words, return all starting indices in s of substrings that are a concatenation of every word in words exactly once, in any order. No overlapping indices for the same substring; each word must appear exactly once.
- All words have the same length
wLen; the concatenation has lengthwLen * words.length. So we only need to check starting positions such that there are enough characters left. - Words can repeat in
words(e.g.["foo","bar","foo"]means we need two "foo" and one "bar").
02
Key Observations
- We can try each possible "phase"
start % wLen: for a fixed phase, we slicesinto segments of lengthwLenstarting atstart, start+wLen, .... Then we slide a window over these segments and check if the window's word counts match the required counts fromwords. - Build a map
need: word → count required. For each phase, maintain a sliding window of segments; add the segment at the right, remove from the left when the window has too many of some word. When the window exactly matchesneed, record the start index. - There are
wLenphases and each phase is O(n/wLen) steps, so total O(n).
03
Approach
High-level: For each starting phase 0..wLen-1, treat s as a sequence of words of length wLen. Use a sliding window over these words; maintain a count map for the current window. When it matches the required map (from words), add the current start index to the result.
Steps:
- Build
need: map each word inwordsto its required count.wLen = words[0].length(),totalLen = wLen * words.length. - For
start = 0..wLen-1: slide a window oversin steps ofwLen(extract wordss[start], s[start+wLen], ...). Maintainwindowcounts; when we have exactlyneed, addstartto result; shrink window if a word count exceeds required. - Return the list of start indices.
04
Implementation
Java
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> result = new ArrayList<>();
int n = s.length(), wCount = words.length, wLen = words[0].length();
Map<String, Integer> need = new HashMap<>();
for (String w : words) {
need.merge(w, 1, Integer::sum);
}
for (int start = 0; start < wLen; start++) {
Map<String, Integer> seen = new HashMap<>();
int count = 0;
for (int i = start; i + wLen <= n; i += wLen) {
String w = s.substring(i, i + wLen);
if (need.containsKey(w)) {
seen.merge(w, 1, Integer::sum);
count++;
while (seen.get(w) > need.get(w)) {
String remove = s.substring(start, start + wLen);
seen.merge(remove, -1, Integer::sum);
count--;
start += wLen;
}
if (count == wCount) {
result.add(start);
}
}
else {
seen.clear();
count = 0;
start = i + wLen;
}
}
}
return result;
}05
Test Cases
Example 1
Input
s = "barfoothefoobarman", words = ["foo","bar"]
Expected
[0,9]Explanation
Substrings "barfoo" and "foobar".
06
Complexity Analysis
- Time: O(n * wLen).
- Space: O(words.length).
07
Follow-ups
- What if words have different lengths? (Then it becomes a different problem.)