Minimum Window Substring
Minimum Window Substring is LeetCode 76 (Hard). You are given two 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 "".
- "Including duplicates" means a character that appears k times in
tmust appear at least k times in the window. - The characters inside the window do not have to be in the same order as in
t, and extra characters are allowed. - The answer is guaranteed to be unique.
Both strings can be 10⁵ characters long, so checking every substring is far too slow; the target is one linear pass.
- m == s.length
- n == t.length
- 1 <= m, n <= 10⁵
- s and t consist of uppercase and lowercase English letters.
Intuition
Checking every substring of s is O(n²) substrings with an O(n) check each. One left-to-right pass is enough. Keep a sliding window s[left..right] (a stretch of s whose two ends only ever move right), because of one property:
- if a window covers
t, every larger window around it also coverst; - if a window does not cover
t, no smaller window inside it does.
So the right end grows the window until it covers t, and the left end shrinks it while it still does. A small count of what is still missing answers "does it cover t?" in O(1).
"Shortest (or longest) contiguous substring that satisfies a condition", where the condition gets easier to satisfy as the window grows, is a variable-size sliding window. Here the condition is "contains all of t". Related: longest substring without repeating characters, permutation in string, find all anagrams. Of that family, the minimum window substring LeetCode problem is the one rated Hard, because duplicates in t must be counted copy by copy.
Approach
Before reading on: for s = "ADOBECODEBANC", move a right pointer until the window contains A, B and C. How far can you then move the left pointer? What single number tells you, without rescanning, whether the window still covers t?
Two ways to solve it
right grows the window until missing hits 0, then left shrinks it while it still covers t.
- Passes: each character enters and leaves once.
- Cover check: O(1) through
missing. - Fits: the full 10⁵ limit.
The accepted answer, and the one interviewers expect.
For each start i, extend an end pointer until the window covers t, and keep the shortest one found.
- Passes: one fresh scan per start.
- Cover check: the same
missingcounter. - Fits: short strings only.
A fine first idea, but it times out at m = 10⁵.
The scan from every start repeats work the sliding window keeps, so the window wins by a factor of m. The steps, code and live demo below follow the sliding window; the per-start code comes after the demo.
Count what t needs
need[c] = occurrences of c in t. missing = len(t). The best window starts empty (length infinity). need tracks each character's shortfall, and missing turns "does the window cover t?" into one comparison.
Expand with right
For each right, take ch = s[right]:
- if
need[ch] > 0, this copy is still required, somissing -= 1; - then
need[ch] -= 1either way; a negative value just means a surplus copy.
Only copies that were still required reduce missing.
Shrink while the window covers t
While missing == 0, the window covers t:
- record it if it is the shortest so far, while it still covers
t; - drop
s[left]withneed[s[left]] += 1; - if that value becomes positive, a required copy just left, so
missing += 1; - move
leftforward.
Return the best window
If no window ever covered t, return ""; otherwise return s[best_start : best_start + best_len]. best_len stays infinite only if t could never be covered.
Minimum Window Substring solution in Python | C++ | Java
need says how many copies of each character of t the window still lacks; missing is their total. The window starts empty, so it lacks all 3.missing; extras only pushed their need below 0. Now missing = 0: the window "ADOBEC" contains all of t.t and is the shortest so far, so record it before shrinking. Dropping A removes a copy t needs (need[A] becomes 1), so missing rises to 1 and the window must grow again.missing; extras only pushed their need below 0. Now missing = 0: the window "DOBECODEBA" contains all of t.t. Keep shrinking.t. Keep shrinking.t. Keep shrinking.t needs (need[C] becomes 1), so missing rises to 1 and the window must grow again.missing; extras only pushed their need below 0. Now missing = 0: the window "ODEBANC" contains all of t.t. Keep shrinking.t. Keep shrinking.t and is the shortest so far, so record it before shrinking. Dropping E costs nothing: it is not in t. Keep shrinking.t and is the shortest so far, so record it before shrinking. Dropping B removes a copy t needs (need[B] becomes 1), so missing rises to 1 and the window must grow again.t is "BANC". Both pointers only moved forward, so the whole scan is O(|s| + |t|).need says how many copies of each character of t the window still lacks; missing is their total. The window starts empty, so it lacks all 3.missing; extras only pushed their need below 0. Now missing = 0: the window "AXAAB" contains all of t.t and is the shortest so far, so record it before shrinking. Dropping A costs nothing: the window had a spare copy. Keep shrinking.t and is the shortest so far, so record it before shrinking. Dropping X costs nothing: it is not in t. Keep shrinking.t and is the shortest so far, so record it before shrinking. Dropping A removes a copy t needs (need[A] becomes 1), so missing rises to 1 and the window must grow again.t is "AAB". Both pointers only moved forward, so the whole scan is O(|s| + |t|).need says how many copies of each character of t the window still lacks; missing is their total. The window starts empty, so it lacks all 2.right reached the end of s and the window still lacks 1 character of t. No window can cover it.missing never reached 0, so the best length is still infinite and the empty string is returned.Brute force from every start
For each start i, copy the counts of t and move an end pointer j until missing reaches 0. That s[i..j] is the shortest window starting at i, so stop there and compare it with the best so far.
Common pitfalls
Counting distinct characters instead of copies
missing = len(set(t))
missing = len(t)
With t = "AAB", a window holding one A and one B would be accepted. missing must count every required copy, and it only drops when a copy is still needed (need[ch] > 0).
Decrementing missing for surplus characters
need[ch] -= 1 missing -= 1
if need[ch] > 0:
missing -= 1
need[ch] -= 1Then every character counts toward t, even ones it does not contain or already has enough of. For "ADOBECODEBANC" the window "ADO" already brings missing to 0 and is returned, although it has no B or C.
Recording the window after shrinking
while missing == 0:
... # drop s[left]
left += 1
best = min(best, right - left + 1)while missing == 0:
best = min(best, right - left + 1)
... # then drop s[left]After dropping s[left] the window may no longer cover t, so its length is not a valid answer. Record first, then shrink.
Edge cases
missing never reaches 0, so the best length stays infinite and "" is returned.
'a' and 'A' are different characters. A 128-entry array indexed by character code handles both without extra work.
Complexity
m = len(s), n = len(t). Counting t is O(n). right visits each character of s once, and left never passes right, so each character enters and leaves the window at most once. The count array has a fixed 128 entries.Minimum Window Substring FAQ
How do you solve minimum window substring?
- Counts:
need[c]for each character oft, andmissing = len(t). - Expand: move
right; ifneed[s[right]] > 0, decrementmissing; always decrementneed[s[right]]. - Shrink: while
missing == 0, record the window, then removes[left](increment itsneed; if it becomes positive, incrementmissing) and moveleft. - Answer: the shortest recorded window, or
"". - Complexity: O(|s| + |t|) time, O(1) extra space for a fixed alphabet.
What is the time complexity of minimum window substring?
O(|s| + |t|). Counting t is O(|t|); each character of s is added once by right and removed at most once by left. Space is O(1) for a fixed character set, or O(k) for k distinct characters with a hash map.