Topics › Sliding Window and Two Pointers › Hard Problems
Minimum Window Substring
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given two strings s (length n) and t (length m), return the shortest substring of s that contains every character of t, including duplicates. If t contains two 'a's, the window must contain at least two 'a's. If no such window exists, return the empty string "". When several shortest windows exist, return the leftmost one (LeetCode guarantees the answer is unique).
Constraints: 1 ≤ n, m ≤ 10^5, and both strings consist of English letters, so a 256-entry count table covers every character.
Example 1
s = "ADOBECODEBANC", t = "ABC"
answer = "BANC" // the only length-4 window with an A, a B and a C
Example 2
s = "a", t = "aa"
answer = "" // t needs two a's; s has only one
Approach 1: Brute force
Intuition. For each start i, extend j until s[i..j] covers t. The first such j gives the shortest valid window starting at i, because extending further only makes it longer. Keep the shortest over all starts.
To test coverage in O(1) per step, keep a table need[c]: how many more copies of c we still need. It starts as the character counts of t. Also keep missing, the total number of required characters not yet covered, starting at m. When s[j] arrives:
- if
need[s[j]] > 0, this copy satisfies a requirement, somissingdrops by one; - either way,
need[s[j]]decreases (it can go negative, which simply means “surplus copies”).
The window covers t exactly when missing == 0.
Algorithm.
- If
m > n, return"". - For each start
i: rebuildneedfromt;missing = m. - For
j = i ..: updateneedandmissingfors[j]. Whenmissing == 0, comparej - i + 1with the best, then break. - Return the best window or
"".
static String bruteForce(String s, String t) {
int n = s.length(), m = t.length();
if (m == 0 || m > n) return "";
int bestLen = Integer.MAX_VALUE, bestStart = -1;
for (int i = 0; i < n; i++) {
int[] need = new int[256];
for (int c = 0; c < m; c++) need[t.charAt(c)]++;
int missing = m; // characters of t not yet covered by s[i..j]
for (int j = i; j < n; j++) {
if (need[s.charAt(j)] > 0) missing--;
need[s.charAt(j)]--;
if (missing == 0) { // shortest valid window that starts at i
if (j - i + 1 < bestLen) {
bestLen = j - i + 1;
bestStart = i;
}
break;
}
}
}
return bestStart == -1 ? "" : s.substring(bestStart, bestStart + bestLen);
}
Complexity. Time O(n · (n + m)): for each of the n starts we rebuild the table in O(m) (plus a constant 256) and scan up to n characters. Space O(1): the table has a fixed size.
The waste: after finding that s[i..j] covers t, the next start i + 1 re-scans almost the same characters. The window s[i+1..j] is what we should test next, not a fresh scan from i + 1.
Approach 2: Optimal, expand and contract
Intuition. Coverage is monotonic. If s[l..r] covers t, so does every window containing it. If it does not, neither does any window inside it. That gives the two-pointer rhythm:
- Expand
runtil the window coverst. Nows[l..r]is valid but possibly bloated on the left. - Contract
lwhile the window still coverst, recording the length each time. When droppings[l]breaks coverage (a required copy left the window), stop and go back to expanding.
Every valid window that could be the answer is examined: for each right end r, the contraction stops at the largest l for which s[l..r] still covers t, which is the shortest valid window ending at r. And neither pointer ever moves backwards, so the total work is linear.
The same need table and missing counter from Approach 1 make every step O(1). When removing s[l], increment need[s[l]]; if it becomes positive, that copy was required (not surplus), so missing increases.
Algorithm.
- If
m > n, return"". Buildneedfromt;missing = m;l = 0. - For each
r: ifneed[s[r]] > 0,missing--; thenneed[s[r]]--. - While
missing == 0: records[l..r]if shorter than the best;need[s[l]]++; if it is now positive,missing++;l++. - Return the best window or
"".
static String optimal(String s, String t) {
int n = s.length(), m = t.length();
if (m == 0 || m > n) return "";
int[] need = new int[256]; // need[c] > 0: still missing; < 0: surplus copies in the window
for (int c = 0; c < m; c++) need[t.charAt(c)]++;
int missing = m, l = 0, bestLen = Integer.MAX_VALUE, bestStart = -1;
for (int r = 0; r < n; r++) {
if (need[s.charAt(r)] > 0) missing--;
need[s.charAt(r)]--;
while (missing == 0) { // window s[l..r] covers t: record, then try to shrink
if (r - l + 1 < bestLen) {
bestLen = r - l + 1;
bestStart = l;
}
need[s.charAt(l)]++;
if (need[s.charAt(l)] > 0) missing++; // we just dropped a required copy
l++;
}
}
return bestStart == -1 ? "" : s.substring(bestStart, bestStart + bestLen);
}
Dry run on s = "ADOBECODEBANC", t = "ABC" (only the steps where the window becomes valid):
| r | char | valid window on entry | windows recorded while shrinking | coverage breaks when dropping | best |
|---|---|---|---|---|---|
| 5 | C | “ADOBEC” (0..5) | “ADOBEC” | A at 0 | “ADOBEC” |
| 10 | A | “DOBECODEBA” (1..10) | “DOBECODEBA” down to “CODEBA” | C at 5 | “ADOBEC” |
| 12 | C | “ODEBANC” (6..12) | “ODEBANC” down to “BANC” | B at 9 | “BANC” |
At r = 10 the new A restores coverage. Contraction drops D, O, B, E (the B at index 3 is surplus because another B sits at index 9) and stops once the C at index 5 leaves. Its shortest record, "CODEBA", only ties "ADOBEC" and does not replace it. At r = 12 the new C restores coverage; contraction from l = 6 peels O, D, E and records "BANC" before the B at index 9 leaves.
Complexity. Time O(n + m): building the table costs m, and l and r each move at most n times. Space O(1): a fixed 256-entry table.
Edge cases and pitfalls
tlonger thans. No window can cover it; return""immediately.- Duplicates in
t.t = "aa"needs two'a's. Using a set instead of counts would wrongly accept a window with one'a'. - Surplus characters.
needmay go negative; that is intended. Only the transition to a positive value on removal means a requirement was broken. Testingneed[c] == 0instead of> 0gives wrong answers when the window holds extra copies. - Recording before shrinking. Record the window inside the
whileloop before removings[l]. Recording after the loop would capture an invalid window. - Case sensitivity.
'A'and'a'are different characters; the 256-entry table keeps them apart. - Returning the substring. Store the start and length, and call
substringonce at the end. Building a new string at every improvement costsO(n)each time.
Related problems
- Minimum Window Subsequence requires the characters of
tin order, which changes the technique. - Number of Substrings Containing All Three Characters counts windows that cover a fixed set instead of finding the shortest.
- Longest Substring Without Repeating Characters is the mirror image: the longest valid window rather than the shortest.
- Check if two strings are anagram of each other uses the same character-count table.