Home
DSA Patterns
Sliding Window
Minimum Window Substring
Minimum Window Substring
Find the shortest part of s that contains every character of t, duplicates included, with a window that grows until it covers t and shrinks while it still does.
Problem Understanding
Minimum Window Substring: given strings s and t, return the shortest substring of s that contains every character of t, including duplicates. If no substring does, return "".
Example: s = "ADOBECODEBANC", t = "ABC" returns "BANC".
Attempt 1: Check Every Substring
Try every start and every end, and test whether that substring contains t: O(|s|²) substrings, each checked in O(|s|) or with counts — O(|s|³) or O(|s|²) at best. Most of that work is repeated: the substring starting one index later shares almost everything with the one before it.
The Intuition: Grow Until Covered, Shrink While Covered
Two facts make a sliding window work:
- If
s[left..right]coverst, any longer window containing it does too — so there is no point extending a covering window. - If
s[left..right]does not covert, no shorter window ending atrightdoes either — so there is no point shrinking an uncovered one.
So right extends until the window covers t; then left advances while it still covers t, recording the shortest. To test coverage in O(1), keep need[c] — how many more of c the window needs, negative for spare copies — and missing, how many characters of t are still uncovered. The window covers t exactly when missing == 0.
Interactive Walkthrough
The window is framed between L and R: dashed while it is missing something, solid once it covers t. The need map beside it ticks each character off as it is satisfied. Watch the frame grow to "ADOBEC", lose the A, regrow, and shrink down to "BANC".
Dry Run Table
Input: s = "ADOBECODEBANC", t = "ABC" (the moments the window changes state)
right |
Event | Window | missing |
Shortest |
|---|---|---|---|---|
| 0–5 | Read A, B, C |
ADOBEC |
0 | ADOBEC (6) |
| — | Drop A at 0 |
DOBEC |
1 | ADOBEC |
| 6–10 | Read up to A at 10 |
DOBECODEBA |
0 | ADOBEC |
| — | Drop D, O, spare B, E |
CODEBA |
0 | ADOBEC (not shorter) |
| — | Drop C at 5 |
ODEBA |
1 | ADOBEC |
| 11–12 | Read N, C |
ODEBANC |
0 | ADOBEC |
| — | Drop O, D |
EBANC |
0 | EBANC (5) |
| — | Drop E |
BANC |
0 | BANC (4) |
| — | Drop B at 9 |
ANC |
1 | BANC |
right has reached the end, so return "BANC".
The Solution Template
Counts of what is still needed, and one integer for how much.
Minimum Window Substring Code
1var minWindow = function(s, t) {2 const need = new Array(128).fill(0);3 for (const c of t) need[c.charCodeAt(0)]++;4 let missing = t.length, left = 0, start = 0, size = Infinity;5 for (let right = 0; right < s.length; right++) {6 const ch = s.charCodeAt(right);7 if (need[ch] > 0) missing--;8 need[ch]--;9 while (missing === 0) {10 if (right - left + 1 < size) {11 start = left;12 size = right - left + 1;13 }14 const out = s.charCodeAt(left);15 need[out]++;16 if (need[out] > 0) missing++;17 left++;18 }19 }20 return size === Infinity ? "" : s.slice(start, start + size);21};
Edge Cases & Common Mistakes
- No covering window (
s = "a",t = "aa"):missingnever reaches 0 — return"". - Duplicates in
t:t = "AAB"needs twoAs; counting distinct characters instead of occurrences gives wrong answers. - Spare copies: a second
Bwhentneeds one drivesneed['B']negative; dropping it later must not raisemissing. - Comparing two maps on every step: correct but slower;
missingmakes the coverage test O(1). - Case sensitivity:
'a'and'A'are different characters.
Summary
Extend right until the window covers t, then shrink from left while it still does, recording the shortest. need counts what is still wanted and missing makes coverage an O(1) check. O(|s| + |t|) time, O(k) space.
