The problem. Given strings s and t, return the shortest substring of s that contains every character of t, counting duplicates (if t has two a's, the window needs two a's). If there is none, return "". The answer is unique when it exists.
s = "ADOBECODEBANC", t = "ABC" -> "BANC"
s = "a", t = "a" -> "a"
s = "a", t = "aa" -> "" (s has only one "a")This is the variable-size window, used to find the shortest valid window. Move right to take in characters until the window covers t. Then move left to drop characters for as long as the window still covers t, recording each covering window's length. When dropping a character breaks the cover, go back to growing. Every character enters once and leaves once.
The bookkeeping is what makes it O(1) per step. need[ch] says how many more copies of ch the window still lacks — it can go below zero when the window holds spare copies. missing is the total still lacking: the window covers t exactly when missing == 0, so there's never a need to compare whole count tables.
From each start, extend to the right until the window covers t; that's the shortest one from this start. Keep the best over all starts.
function minWindow(s, t) { const need = new Map(); for (const ch of t) need.set(ch, (need.get(ch) ?? 0) + 1); let best = ""; for (let i = 0; i < s.length; i++) { const have = new Map(); let covered = 0; // distinct characters with enough copies for (let j = i; j < s.length; j++) { const ch = s[j]; have.set(ch, (have.get(ch) ?? 0) + 1); if (have.get(ch) === need.get(ch)) covered++; if (covered === need.size) { // s[i..j] covers t: the shortest from i if (best === "" || j - i + 1 < best.length) best = s.slice(i, j + 1); break; } } } return best;}Time O(n²), space O(1) beyond the answer (the counters have 128 slots, for ASCII). Each start rescans the characters the previous start already counted.
Grow until missing is 0, then shrink while it stays 0; the green window is the best so far.
function minWindow(s, t) { const need = new Map(); // char -> how many more the window needs for (const ch of t) need.set(ch, (need.get(ch) ?? 0) + 1); let missing = t.length; // characters of t not yet covered let left = 0, bestStart = 0, bestLen = Infinity; for (let right = 0; right < s.length; right++) { const ch = s[right]; if (need.has(ch)) { if (need.get(ch) > 0) missing--; // this one was still needed need.set(ch, need.get(ch) - 1); // (below 0 means a spare copy) } while (missing === 0) { // the window covers t: try to shrink it if (right - left + 1 < bestLen) { bestStart = left; bestLen = right - left + 1; } const out = s[left++]; if (need.has(out)) { need.set(out, need.get(out) + 1); if (need.get(out) > 0) missing++; // dropped a character we still needed } } } return bestLen === Infinity ? "" : s.slice(bestStart, bestStart + bestLen);}Time O(n + m), space O(1) for n = |s| and m = |t|: counting t is O(m), and left and right each pass over s once. (The Java and C++ versions use a 128-slot array instead of a map: characters outside t simply go negative and never matter.)
This is the hardest of the window problems because it combines everything: counts with duplicates, a single number that says valid or not, and a shrink loop. If you can explain missing, you can do the rest.
"".t = "aa" needs two a's in the window.'a' and 'A' are different characters; a 26-slot array isn't enough.