Coding Interview Patterns

Course Content

Coding Interview Patterns

20 sections · 146 lessons

Minimum Window Substring


This is the hardest of the standard window problems and a regular in senior interviews. Everything in it has appeared before: a count map, the shortest-window template, and a counter that avoids comparing whole maps. The difficulty is keeping those three pieces exactly in step.

Cover A, B and C, then shrink while coveredAXBYCABZ01234567startendformed = 3 here; removing C drops it to 2, so CAB of length 3 is the shortest cover.
Expand until every required letter has enough copies, then shrink while it still does, recording each valid length.

The problem

Given strings s and t, return the shortest contiguous substring of s that contains every character of t, including repeats. If there is none, return the empty string. If several shortest ones exist, return the one that starts first.

  • s = "AXBYCABZ", t = "ABC" → "CAB". It holds one A, one B and one C in three characters; nothing shorter does.
  • s = "A", t = "AA" → "". t needs two As and s has only one.

Constraints: 1 ≤ lengths ≤ 10⁵; upper- and lowercase English letters.

Clarifying questions

  • Do repeats in t count? Yes. t = "AAB" needs two As in the window.
  • Case-sensitive? Yes: a and A are different.
  • Which answer if several are shortest? Assume the leftmost; the code below keeps the first one it finds.

Approach 1: every start, grow until it covers t

For each start, extend the end until the window has enough of every character of t. That is the shortest window for this start, so stop there.

Python
from collections import Counterdef min_window_brute(s: str, t: str) -> str:    """Every start, grow until the window covers t: O(n^2 * |alphabet|)."""    if not t:        return ""    need = Counter(t)    best = ""    for start in range(len(s)):        have: Counter[str] = Counter()        for end in range(start, len(s)):            have[s[end]] += 1            if all(have[c] >= n for c, n in need.items()):                if not best or end - start + 1 < len(best):                    best = s[start:end + 1]                break    return best

Time: O(n² · d), where d is the number of distinct characters in t, because each check loops over need. At n = 10⁵ that is over 10¹⁰ operations.

The key insight

Two ideas turn this into one pass.

It is a shortest window. Once a window covers t, making it longer keeps it covering t. So the rule "covers t" only gets easier as the window grows, and the shortest-window template applies: expand end until the window is valid, then shrink start while it is still valid, recording each valid length, until it breaks.

Validity must be O(1). Comparing two count maps each step costs O(d). Instead keep one integer, formed: how many distinct characters of t currently have enough copies in the window. The window covers t exactly when formed == required, where required is the number of distinct characters in t.

formed changes only at exact boundary crossings:

  • When a character enters and its count becomes equal to what t needs, formed goes up by one. Going from 2 to 3 copies when only 1 is needed changes nothing.
  • When a character leaves and its count drops below what t needs, formed goes down by one.

Approach 2: shrink while valid, with formed

Python
from collections import Counterdef min_window(s: str, t: str) -> str:    """Shortest substring of s containing every character of t (with repeats)."""    if not s or not t:        return ""    need = Counter(t)    required = len(need)             # distinct characters that must be satisfied    formed = 0                       # how many of them currently are    have: dict[str, int] = {}    start = 0    best_len = float("inf")    best_start = 0    for end, char in enumerate(s):        have[char] = have.get(char, 0) + 1        if char in need and have[char] == need[char]:            formed += 1        while formed == required:    # valid: record, then try a shorter window            if end - start + 1 < best_len:                best_len = end - start + 1                best_start = start            leaving = s[start]            have[leaving] -= 1            if leaving in need and have[leaving] < need[leaving]:                formed -= 1          # this requirement just broke            start += 1    return "" if best_len == float("inf") else s[best_start:best_start + best_len]

Dry run on s = "AXBYCABZ", t = "ABC" (required = 3):

EventWindowformedBest so far
end 0–3: A, X, B, Y enterAXBY2none
end 4: C entersAXBYC (0–4)3, validAXBYC (5)
shrink: remove Astart = 12, brokenAXBYC
end 5: A entersXBYCA (1–5)3, validlength 5, no change
shrink: remove XBYCA (2–5)still 3BYCA (4)
shrink: remove Bstart = 32, brokenBYCA
end 6: B entersYCAB (3–6)3, validlength 4, no change
shrink: remove YCAB (4–6)still 3CAB (3)
shrink: remove Cstart = 52, brokenCAB
end 7: Z entersABZ (5–7)2CAB

The answer is "CAB". Notice that removing X and Y never touched formed: they are not in t.

Time: O(|s| + |t|). Building need is O(|t|); each index of s enters once and leaves at most once. Space: O(d) for the two maps, where d is the number of distinct characters (at most 52 here).

Edge cases

  • t longer than s, or no valid window. formed never reaches required, best_len stays infinite, and the function returns "".
  • Repeats in t. For t = "AAB", need["A"] = 2, so formed counts A only when the second A enters, and uncounts it as soon as one leaves.
  • Characters not in t. They go into have but never change formed. (You can skip counting them at all; it saves a little memory.)
  • s equals t. The first valid window is the whole string, and removing any character breaks it.

Follow-ups

  • "Return only the length." Return best_len, or 0 if it stays infinite.
  • "t's characters must appear in order (a subsequence, not a multiset)." No longer a count window; it needs a two-pointer scan forward and backward, or dynamic programming (Minimum Window Subsequence).
  • "s is huge and t is tiny." Filter s first to the positions whose character is in t, then run the same window over that shorter list of (index, character) pairs.