The problem
You get two strings s and t. Return the shortest substring of s that contains every character of t, including duplicates (if t has two As, the window needs at least two). If no such substring exists, return "".
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC""BANC" contains an A, a B and a C, and no shorter piece of s does.
How to think about it
Start with brute force. Check every substring of s, test whether it covers t’s counts, and keep the shortest. That’s O(n²) substrings, each with a coverage check: O(n²) at best, O(n³) if done naively.
Notice the monotonic shape. If a window covers t, every bigger window that contains it also covers t. If a window doesn’t cover t, every window inside it doesn’t either. That’s exactly when the grow-then-shrink sliding window works.
The key insight: two counters make coverage an O(1) check. Keep need[c]: how many more of c the window still needs (it goes negative when the window has spares). Keep missing: the total number of characters of t still not covered. When a new character arrives and need[c] > 0, it was genuinely needed, so missing drops by one. Either way, need[c] drops by one. When missing hits 0, the window covers t.
Then shrink to the smallest valid window. From the left, any character with need[c] < 0 is a spare (or not in t at all), so you can drop it without breaking coverage. Stop at the first character the window actually needs. Compare that window with the best so far. Every character enters once and leaves once, so it’s O(n).
Which pattern is this? A variable-size sliding window, “minimum length” flavour, with a frequency map. The tell is “smallest substring that contains all of …”.
Approach
- Return
""if either string is empty. Setneed = Counter(t),missing = len(t),lo = 0, andstart = end = 0for the best window. - For each right edge
r(1-indexed, so the window iss[lo:r]) with characterc: ifneed[c] > 0, decreasemissing. Then decreaseneed[c]. - If
missing == 0, moveloright whileneed[s[lo]] < 0, adding each dropped character back intoneed. - If this window is the first valid one or shorter than the best, save
start, end = lo, r. - Return
s[start:end], which is""if no window was ever valid.
Solution
from collections import Counter
def min_window(s, t):
if not t or not s: return ""
need = Counter(t); missing = len(t); start = end = 0; lo = 0
for r, c in enumerate(s, 1): # window is s[lo:r]
if need[c] > 0: missing -= 1 # a character we still needed
need[c] -= 1
if missing == 0: # window covers t
while need[s[lo]] < 0: # drop spares from the left
need[s[lo]] += 1; lo += 1
if end == 0 or r - lo < end - start: start, end = lo, r
return s[start:end]
# min_window("ADOBECODEBANC", "ABC") -> "BANC"Dry run
On s = "ADOBECODEBANC", t = "ABC" (only the steps that change something important):
r |
c |
missing |
lo after shrinking |
Window s[lo:r] |
Best so far |
|---|---|---|---|---|---|
| 1 | A | 2 | 0 | — | — |
| 4 | B | 1 | 0 | — | — |
| 6 | C | 0 | 0 | ADOBEC | ADOBEC |
| 10 | B | 0 | 0 | ADOBECODEB | ADOBEC |
| 11 | A | 0 | 5 | CODEBA | ADOBEC (same length) |
| 13 | C | 0 | 9 | BANC | BANC |
The answer is “BANC”.
Complexity
- Time: O(|s| + |t|). Building
needis O(|t|); thenrandloeach move throughsonce. - Space: O(charset). The
needmap holds the characters oftplus any others seen ins.
Edge cases and pitfalls
- No valid window (
s = "a",t = "b"):endstays 0 ands[0:0]is"". - Duplicates in
t(t = "AA") are handled by counts. A set-based check would accept a singleA. tlonger thanscan never be covered and returns"".- Shrinking while
missing == 0(instead of while the left character is a spare) removes a needed character and loses the window. Shrink only past characters withneed < 0. - Comparing full
Counters on every step is correct but O(charset) per step; themissingcounter makes the check O(1).
Related problems
- Minimum Size Subarray Sum (LeetCode 209): the same grow-then-shrink template with a numeric sum.
- Permutation in String (LeetCode 567): a fixed-size window that must match counts exactly.
- Longest Substring Without Repeating Characters (LeetCode 3): the “longest valid window” side of the pattern.
- 1.Best Time to Buy and Sell Stock (LeetCode 121)
- 2.Maximum Average Subarray I (LeetCode 643)
- 3.Longest Substring Without Repeating Characters (LeetCode 3)
- 4.Longest Repeating Character Replacement (LeetCode 424)
- 5.Permutation in String (LeetCode 567)
- 6.Find All Anagrams in a String (LeetCode 438)
- 7.Fruit Into Baskets (LeetCode 904)
- 8.Max Consecutive Ones III (LeetCode 1004)
- 9.Minimum Size Subarray Sum (LeetCode 209)
- 10.Longest Subarray of 1's After Deleting One Element (LeetCode 1493)
- 11.Sliding Window Maximum (LeetCode 239)
- 12.Minimum Window Substring (LeetCode 76)