19.Minimum Window Substring
Problem
Given strings s and t, return the shortest substring of s that contains every character of t, including duplicates. If no such substring exists, return the empty string "".
The test data guarantees the answer is unique.
Examples
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: "BANC" contains 'A', 'B' and 'C' and no shorter window does.Input: s = "a", t = "a"
Output: "a"Input: s = "a", t = "aa"
Output: ""
Explanation: t needs two 'a's but s has only one.Constraints
m == s.length,n == t.length1 <= m, n <= 10^5sandtconsist of uppercase and lowercase English letters.
๐ก Key Insight
Use a variable-size sliding window. Expand right until the window covers t; then shrink left as far as possible while it still covers t, recording the smallest valid window. To test "covers t" in O(1), keep a single counter missing = how many characters of t (with multiplicity) the window still lacks; it only changes when a needed character enters or leaves.
Brute Force
TimeO(mยฒ ยท 128)SpaceO(1)Intuition
Check every substring s[i..j]: count its characters and test whether each character of t appears at least as many times. Keep the shortest one that passes. Extending j incrementally avoids recounting from scratch.
Algorithm
- Build
need[128]fromt. - For each start
i: resethave[128]; for each endj >= i, adds[j]tohave. - If
have[c] >= need[c]for everyc, records[i..j]if shorter than the best, thenbreak(longer windows fromicannot be shorter). - Return the best window or
"".
Visualization
s = "ADOBECODEBANC", t = "ABC"
i=0: A D O B E C covers at j=5 โ "ADOBEC" (6)
i=1: D O B E C O D E B A covers at j=10 โ length 10
...
i=9: B A N C covers at j=12 โ "BANC" (4) โ shortestCode
class Solution {
public String minWindow(String s, String t) {
int[] need = new int[128];
for (char c : t.toCharArray()) {
need[c]++;
}
int bestStart = 0, bestLen = Integer.MAX_VALUE;
for (int i = 0; i < s.length(); i++) {
int[] have = new int[128];
for (int j = i; j < s.length(); j++) {
have[s.charAt(j)]++;
if (covers(have, need)) {
if (j - i + 1 < bestLen) {
bestLen = j - i + 1;
bestStart = i;
}
break; // extending j further only makes it longer
}
}
}
return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestStart, bestStart + bestLen);
}
private boolean covers(int[] have, int[] need) {
for (int c = 0; c < 128; c++) {
if (have[c] < need[c]) {
return false;
}
}
return true;
}
}Complexity
- Time: O(mยฒ ยท 128) โ O(mยฒ) windows, each checked against 128 counters.
- Space: O(1) โ fixed-size count arrays.
Sliding Window + Full Coverage Check
TimeO(128 ยท m)SpaceO(1)Intuition
If s[left..right] covers t, then any window that starts at left and ends later also covers it โ so once covered we should try moving left forward instead. Use two pointers: grow right, and while the window covers t, record it and shrink from the left. Coverage is still tested by scanning all 128 counters.
Algorithm
- Build
need;left = 0,have[128]. - For each
right: adds[right]tohave. - While
covers(have, need): record the window if shorter; removes[left]fromhave;left++. - Return the best window or
"".
Dry Run
s = "ADOBECODEBANC", t = "ABC"
right=5 "ADOBEC" covers โ record 6, shrink: drop A โ no longer covers
right=10 "DOBECODEBA" covers โ shrink to "CODEBA" (6), then drop C โ stop
right=12 "ODEBANC" covers โ shrink to "BANC" (4) โ, drop B โ stop
answer = "BANC"Code
class Solution {
public String minWindow(String s, String t) {
int[] need = new int[128];
for (char c : t.toCharArray()) {
need[c]++;
}
int[] have = new int[128];
int left = 0, bestStart = 0, bestLen = Integer.MAX_VALUE;
for (int right = 0; right < s.length(); right++) {
have[s.charAt(right)]++;
// shrink while the window still contains all of t
while (covers(have, need)) {
if (right - left + 1 < bestLen) {
bestLen = right - left + 1;
bestStart = left;
}
have[s.charAt(left)]--;
left++;
}
}
return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestStart, bestStart + bestLen);
}
private boolean covers(int[] have, int[] need) {
for (int c = 0; c < 128; c++) {
if (have[c] < need[c]) {
return false;
}
}
return true;
}
}Complexity
- Time: O(128 ยท m) โ each pointer moves at most
mtimes, each move followed by a 128-slot check. - Space: O(1) โ fixed-size arrays.
Sliding Window + Missing CounterOptimal
TimeO(m + n)SpaceO(1)Intuition
Replace the 128-slot check with one integer. Let need[c] be how many more copies of c the window still requires (it can go negative for surplus). Let missing = total required characters not yet in the window, starting at t.length().
- When
s[right]enters andneed[c] > 0, it fills a real gap โmissing--. Thenneed[c]--. - When
s[left]leaves,need[c]++; if it becomes> 0, we just lost a required character โmissing++.
The window covers t exactly when missing == 0.
Algorithm
need[c]++for eachcint;missing = t.length();left = 0.- For each
right:c = s[right]; ifneed[c] > 0,missing--;need[c]--.- While
missing == 0: record[left, right]if shorter;d = s[left];need[d]++; ifneed[d] > 0,missing++;left++.
- Return the best window or
"".
Dry Run
flowchart TD
A["s = 'ADOBECODEBANC', t = 'ABC'<br/>missing = 3"] --> B["right = 5: 'ADOBEC'<br/>missing = 0, best = 6"]
B --> C["drop 'A' โ missing = 1<br/>left = 1"]
C --> D["right = 10: 'DOBECODEBA'<br/>missing = 0"]
D --> E["shrink past D, O, B, E<br/>('B' surplus, first B leaves freely)<br/>window 'CODEBA', len 6"]
E --> F["drop 'C' โ missing = 1<br/>left = 6"]
F --> G["right = 12: 'ODEBANC'<br/>missing = 0"]
G --> H["shrink past O, D, E<br/>window 'BANC', best = 4"]
H --> I["drop 'B' โ missing = 1<br/>loop ends, return 'BANC'"]Code
class Solution {
public String minWindow(String s, String t) {
if (t.length() > s.length()) {
return "";
}
int[] need = new int[128]; // how many more of each char the window needs
for (char c : t.toCharArray()) {
need[c]++;
}
int missing = t.length(); // required chars not yet covered
int left = 0, bestStart = 0, bestLen = Integer.MAX_VALUE;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (need[c] > 0) {
missing--; // c fills a real requirement
}
need[c]--;
// window covers t: record it and try to shrink
while (missing == 0) {
if (right - left + 1 < bestLen) {
bestLen = right - left + 1;
bestStart = left;
}
char d = s.charAt(left);
need[d]++;
if (need[d] > 0) {
missing++; // we just removed a required char
}
left++;
}
}
return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestStart, bestStart + bestLen);
}
}Complexity
- Time: O(m + n) โ building
needis O(n);leftandrighteach move at mostmtimes. - Space: O(1) โ a fixed 128-slot array (the output substring aside).
Complexity Comparison
| Approach | Time | Space |
|---|---|---|
| Brute Force | O(mยฒ ยท 128) | O(1) |
| Sliding Window + full check | O(128 ยท m) | O(1) |
| Sliding Window + missing counter | O(m + n) | O(1) |
Common Pitfalls
- Ignoring duplicates in
t:t = "AA"needs two'A's โ a set of required letters is not enough. - Decrementing
missingfor every character that enters, even surplus ones โ only decrement whenneed[c] > 0before the decrement. - Building
s.substring(...)inside the loop for every candidate; store(start, length)and build the string once at the end. - Using a 26-slot array โ
sandtmix upper and lowercase letters, so index by the rawchar(128 slots) or use 52.