Algorithm

Minimum Window Substring

Sliding Window Pattern

Minimum Window Substring

Given two strings s and t of lengths m and n respectively, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string "".

CONSTRAINTS
  • m == s.length
  • n == t.length
  • 1 <= m, n <= 10⁵
  • s and t consist of uppercase and lowercase English letters
EXAMPLE 1
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
"BANC" at index 9 contains A, B, and C with length 4. Other windows containing all of t are longer (e.g., "ADOBEC" at index 0 has length 6).
EXAMPLE 2
Input: s = "a", t = "a"
Output: "a"
The entire string is the only window and it exactly contains t.
EXAMPLE 3
Input: s = "a", t = "aa"
Output: ""
t requires two 'a' characters, but s only has one. No valid window exists. Return empty string.
Is the comparison case-sensitive?
Yes. 'A' and 'a' are treated as different characters. Use a map keyed by the exact character, not a case-normalized version.
What if t has duplicate characters like 'aa'?
The window must contain at least as many of each character as t does. For 'aa', the window needs at least 2 'a' characters to be valid.
What if no valid window exists?
Return an empty string.
When do I update the minimum window — inside expansion or contraction?
Inside the contraction loop. You want the smallest valid window, so you record after each left-shrink while the window is still valid, then shrink again to see if you can make it even smaller.

You want the shortest stretch of s that covers everything in t — every character of t, counting duplicates (if t is "aab" the window needs two a's and a b). Return that stretch as a string, or "" if none exists. Because we want the shortest valid window, this is the shortest-window Variable Sliding Window, the same shape as Minimum Size Subarray Sum: expand until valid, then squeeze from the left while it stays valid, recording the smallest. The hard part is not the window — it is deciding, cheaply, when the window is "valid".

Why brute force hurts

Checking every substring and comparing its letter counts to t is around O(m² · alphabet) — endlessly re-counting overlapping ranges.

A cheap validity test: count how many requirements are met

Build need, the count of each character t demands, and let required = len(need) be how many distinct characters must be satisfied. As the window grows, keep have, the window's own counts, and a single number formed: how many distinct required characters currently have enough copies in the window. The window is valid exactly when formed == required. The beauty is that formed updates in O(1) — a character entering can push one requirement from not-quite to just-met (formed += 1), and a character leaving can push one back below its quota (formed -= 1). No rescanning.

Expand to satisfy, squeeze to minimize
python
from collections import Counter
def minWindow(s, t):
    if not s or not t: return ""
    need = Counter(t)
    required = len(need)
    have = {}
    formed = 0
    left = 0
    best_len = float('inf')
    best_start = 0
    for right in range(len(s)):
        c = s[right]
        have[c] = have.get(c, 0) + 1
        if c in need and have[c] == need[c]:   # this char just reached its quota
            formed += 1
        while formed == required:              # valid - record, then squeeze
            if right - left + 1 < best_len:
                best_len = right - left + 1
                best_start = left
            lc = s[left]
            have[lc] -= 1
            if lc in need and have[lc] < need[lc]:   # dropped below quota
                formed -= 1
            left += 1
    return "" if best_len == float('inf') else s[best_start:best_start + best_len]
Crucial Noteformed counts distinct satisfied requirements, not raw characters. We bump it only when a character's count hits exactly its needed amount (have[c] == need[c]) — not on every occurrence — otherwise extra copies of the same letter would over-count and the window would look valid too early. Symmetrically, on the left we drop formed only when a count falls below its quota, so surplus copies leave harmlessly. And note case matters: 'A' and 'a' are different requirements.
Worked Example:s = "ADOBECODEBANC", t = "ABC"
need = {A:1, B:1, C:1}, required = 3.
- Expand to index 5, window "ADOBEC" — has A, B, C, formed = 3, valid. Record length 6. Squeeze: drop 'A' at left, A falls below quota, formed = 2, window now "DOBEC" invalid.
- Expand to index 10, window "...ODEBA" regains an A → "ADOBECODEBA" valid again (length 11, not better). Squeeze forward past the leading A/D/O/B/E/C/O/D/E, dropping surplus letters until only the last A,...,B,A,N,C matter — the window tightens to "BANC" (indices 9–12), formed still 3, length 4 — new best.
- No shorter valid window remains. Answer: "BANC".

If t needs more of a letter than s can offer (s = "a", t = "aa"), formed never reaches required, best_len stays infinite, and we return "".

How it maps onto the pattern

The four slots: (a) invalid = formed < required (so we squeeze while formed == required); (b) expand bumps have and maybe formed; (c) shrinking decrements have and maybe drops formed; (d) record the minimum window, inside the squeeze loop while still valid — the shortest-window timing you first saw in Minimum Size Subarray Sum. The reusable lesson is the formed/required counter: whenever "valid" means satisfying several requirements at once, don't re-check them all each step — track how many are currently satisfied and nudge that one number as characters cross their thresholds.

Interactive Strategy Visualization

Min Window Substring

Target: "ABC"
Frequency Hub
A
B
C
A
0
D
1
O
2
B
3
E
4
C
5
O
6
D
7
E
8
B
9
A
10
N
11
C
12
Found
3 / 3
Min Length
6
O(N² × T) Brute Force
O(N + T) Variable Sliding Window