Shortest window containing every letter
Given strings s and t, return the length of the shortest
contiguous substring of s that contains every character of t, counted
with multiplicity (if t has two a's, the substring needs at least two). Return 0 if no
such substring exists.
Examples
| Input | Output | Why |
|---|---|---|
s = "ADOBECODEBANC", t = "ABC" | 4 | "BANC" is the shortest window holding an A, a B and a C. |
s = "a", t = "aa" | 0 | t needs two a's; s has one. |
s = "acbbaca", t = "aba" | 4 | "baca": two a's and a b. |
Constraints
- 1 ≤ |s|, |t| ≤ 10⁵
- Characters are ASCII letters
Hints
Hint 1
Checking every substring is O(n²) windows, each needing a count. A window that already contains t stays valid when it grows; one that does not stays invalid when it shrinks.
Hint 2
So use two pointers. Move the right end until the window contains t. Then move the left end as far as possible while it still does, recording the length, and repeat.
Hint 3
Keep a count of how many of each character the window still needs, and a single number "missing" for the total still needed. A character only reduces "missing" if it was still needed; that keeps every step O(1).
Where people lose marks
- Comparing whole count tables at every step to test validity. Correct, but it multiplies the running time by the alphabet size; the single "missing" counter avoids it.
- Decrementing "missing" for every character added, including surplus copies. Only a character that was still needed reduces it.
- Recording the window length after removing the left character rather than before. The window was valid before the removal.
- Ignoring multiplicity: treating t as a set of letters makes "aa" look satisfied by a single a.
Dry run on "ADOBECODEBANC","ABC"
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 23sA0left/rightD1O2B3E4C5O6D7E8B9A10N11C12missing2best—
Add "A" at 0. It was still needed, so 2 characters remain missing.
-
Step 2 / 23sA0leftD1rightO2B3E4C5O6D7E8B9A10N11C12missing2best—
Add "D" at 1. It was not needed (a surplus or unrelated character).
-
Step 3 / 23sA0leftD1O2rightB3E4C5O6D7E8B9A10N11C12missing2best—
Add "O" at 2. It was not needed (a surplus or unrelated character).
-
Step 4 / 23sA0leftD1O2B3rightE4C5O6D7E8B9A10N11C12missing1best—
Add "B" at 3. It was still needed, so 1 character remain missing.
-
Step 5 / 23sA0leftD1O2B3E4rightC5O6D7E8B9A10N11C12missing1best—
Add "E" at 4. It was not needed (a surplus or unrelated character).
-
Step 6 / 23sA0leftD1O2B3E4C5rightO6D7E8B9A10N11C12missing0best—
Add "C" at 5. It was still needed, so 0 characters remain missing.
-
Step 7 / 23sA0leftD1O2B3E4C5rightO6D7E8B9A10N11C12missing1best6
Nothing is missing: window [0, 5] of length 6 is valid and is the shortest so far. Drop "A" from the left; the window needs it again, so stop shrinking.
-
Step 8 / 23sA0D1leftO2B3E4C5O6rightD7E8B9A10N11C12missing1best6
Add "O" at 6. It was not needed (a surplus or unrelated character).
-
Step 9 / 23sA0D1leftO2B3E4C5O6D7rightE8B9A10N11C12missing1best6
Add "D" at 7. It was not needed (a surplus or unrelated character).
-
Step 10 / 23sA0D1leftO2B3E4C5O6D7E8rightB9A10N11C12missing1best6
Add "E" at 8. It was not needed (a surplus or unrelated character).
-
Step 11 / 23sA0D1leftO2B3E4C5O6D7E8B9rightA10N11C12missing1best6
Add "B" at 9. It was not needed (a surplus or unrelated character).
-
Step 12 / 23sA0D1leftO2B3E4C5O6D7E8B9A10rightN11C12missing0best6
Add "A" at 10. It was still needed, so 0 characters remain missing.
-
Step 13 / 23sA0D1leftO2B3E4C5O6D7E8B9A10rightN11C12missing0best6
Nothing is missing: window [1, 10] of length 10 is valid. Drop "D" from the left; still valid, keep shrinking.
-
Step 14 / 23sA0D1O2leftB3E4C5O6D7E8B9A10rightN11C12missing0best6
Nothing is missing: window [2, 10] of length 9 is valid. Drop "O" from the left; still valid, keep shrinking.
-
Step 15 / 23sA0D1O2B3leftE4C5O6D7E8B9A10rightN11C12missing0best6
Nothing is missing: window [3, 10] of length 8 is valid. Drop "B" from the left; still valid, keep shrinking.
-
Step 16 / 23sA0D1O2B3E4leftC5O6D7E8B9A10rightN11C12missing0best6
Nothing is missing: window [4, 10] of length 7 is valid. Drop "E" from the left; still valid, keep shrinking.
-
Step 17 / 23sA0D1O2B3E4C5leftO6D7E8B9A10rightN11C12missing1best6
Nothing is missing: window [5, 10] of length 6 is valid and is the shortest so far. Drop "C" from the left; the window needs it again, so stop shrinking.
-
Step 18 / 23sA0D1O2B3E4C5O6leftD7E8B9A10N11rightC12missing1best6
Add "N" at 11. It was not needed (a surplus or unrelated character).
-
Step 19 / 23sA0D1O2B3E4C5O6leftD7E8B9A10N11C12rightmissing0best6
Add "C" at 12. It was still needed, so 0 characters remain missing.
-
Step 20 / 23sA0D1O2B3E4C5O6leftD7E8B9A10N11C12rightmissing0best6
Nothing is missing: window [6, 12] of length 7 is valid. Drop "O" from the left; still valid, keep shrinking.
-
Step 21 / 23sA0D1O2B3E4C5O6D7leftE8B9A10N11C12rightmissing0best6
Nothing is missing: window [7, 12] of length 6 is valid and is the shortest so far. Drop "D" from the left; still valid, keep shrinking.
-
Step 22 / 23sA0D1O2B3E4C5O6D7E8leftB9A10N11C12rightmissing0best5
Nothing is missing: window [8, 12] of length 5 is valid and is the shortest so far. Drop "E" from the left; still valid, keep shrinking.
-
Step 23 / 23sA0D1O2B3E4C5O6D7E8B9leftA10N11C12rightmissing1best4
Nothing is missing: window [9, 12] of length 4 is valid and is the shortest so far. Drop "B" from the left; the window needs it again, so stop shrinking. The right end has reached the end of s; the answer is 4.
How to think about it
Validity is monotone in the window: adding characters to a window that contains
t cannot spoil it, and removing characters from one that does not cannot fix it. That
is the condition under which a variable sliding window works.
- Grow. Advance the right end. If the new character was still needed, one fewer character is missing.
- Shrink. While nothing is missing, the window is valid: record its length, then drop the left character. If the window now needs that character again, it is missing once more and the shrinking stops.
The bookkeeping is a table need[c], started at the counts in t, and a
number missing = |t|. Adding c decrements need[c], and
decrements missing only if need[c] was positive. Characters that
t does not contain go negative and never affect missing; surplus copies of
useful characters do the same. Removing reverses each step.
Each pointer moves at most |s| times, so the algorithm is O(|s| + |t|).
The same loop, with "valid" defined differently, solves most substring problems: the longest substring with at most k distinct characters, all anagrams of t in s, the shortest subarray with sum at least a target. Decide what makes a window valid, and check it is monotone.
Complexity. Time O(|s| + |t|) · Space O(alphabet)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
from collections import Counter
def min_window_cover(s, t):
need = Counter(t) # how many of each character the window still needs
missing = len(t) # total still needed
best = 0
left = 0
for right, c in enumerate(s):
if need[c] > 0:
missing -= 1
need[c] -= 1
while missing == 0: # valid: record, then shrink
size = right - left + 1
if best == 0 or size < best:
best = size
d = s[left]
need[d] += 1
if need[d] > 0: # the window needs d again
missing += 1
left += 1
return best#include <string>
int solve(const std::string& s, const std::string& t) {
int need[128] = {0};
for (unsigned char c : t) need[c]++;
int missing = static_cast<int>(t.size()), best = 0, left = 0;
for (int right = 0; right < static_cast<int>(s.size()); ++right) {
unsigned char c = s[right];
if (need[c] > 0) --missing;
--need[c];
while (missing == 0) { // valid: record, then shrink
int size = right - left + 1;
if (best == 0 || size < best) best = size;
unsigned char d = s[left++];
if (++need[d] > 0) ++missing; // the window needs d again
}
}
return best;
}class Solution {
public int minWindowCover(String s, String t) {
int[] need = new int[128];
for (char c : t.toCharArray()) need[c]++;
int missing = t.length(), best = 0, left = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (need[c] > 0) missing--;
need[c]--;
while (missing == 0) { // valid: record, then shrink
int size = right - left + 1;
if (best == 0 || size < best) best = size;
char d = s.charAt(left++);
if (++need[d] > 0) missing++; // the window needs d again
}
}
return best;
}
}function minWindowCover(s, t) {
const need = new Map();
for (const c of t) need.set(c, (need.get(c) || 0) + 1);
let missing = t.length, best = 0, left = 0;
for (let right = 0; right < s.length; right++) {
const c = s[right];
if ((need.get(c) || 0) > 0) missing--;
need.set(c, (need.get(c) || 0) - 1);
while (missing === 0) { // valid: record, then shrink
const size = right - left + 1;
if (best === 0 || size < best) best = size;
const d = s[left++];
need.set(d, need.get(d) + 1);
if (need.get(d) > 0) missing++; // the window needs d again
}
}
return best;
}fn solve(s: &str, t: &str) -> usize {
let s = s.as_bytes();
let mut need = [0i64; 128];
for &c in t.as_bytes() {
need[c as usize] += 1;
}
let (mut missing, mut best, mut left) = (t.len(), 0usize, 0usize);
for right in 0..s.len() {
let c = s[right] as usize;
if need[c] > 0 {
missing -= 1;
}
need[c] -= 1;
while missing == 0 { // valid: record, then shrink
let size = right - left + 1;
if best == 0 || size < best {
best = size;
}
let d = s[left] as usize;
left += 1;
need[d] += 1;
if need[d] > 0 {
missing += 1; // the window needs d again
}
}
}
best
}import Data.Array
import qualified Data.Map.Strict as M
solve :: String -> String -> Int
solve s t = go 0 0 need0 (length t) 0
where
n = length s
arr = listArray (0, n - 1) s
need0 = M.fromListWith (+) [(c, 1 :: Int) | c <- t]
get m c = M.findWithDefault 0 c m
-- right is the next index to add; while nothing is missing, shrink from the left
go left right need missing best
| missing == 0 =
let size = right - left
best' = if best == 0 || size < best then size else best
d = arr ! left
need' = M.insert d (get need d + 1) need
missing' = if get need' d > 0 then missing + 1 else missing
in go (left + 1) right need' missing' best'
| right == n = best
| otherwise =
let c = arr ! right
missing' = if get need c > 0 then missing - 1 else missing
need' = M.insert c (get need c - 1) need
in go left (right + 1) need' missing' best
Variants to try
- Return the window itself: record its start along with its length.
- Find all start positions of anagrams of t in s: a fixed window of length |t| with the same counts.
- Longest substring with at most k distinct characters: the window is valid while a distinct-count stays at most k, and you record the longest valid window instead of the shortest.
- Smallest range covering one element from each of k sorted lists: merge the lists, then run this window over the merged sequence, where t is "one of each list".
Next problems
Maximum of every window
A deque of candidates, kept in falling order, gives the maximum of every window in one pass.
Open the problem → Medium15 minLongest run of distinct characters
The window that grows on the right and shrinks on the left, and the invariant that makes it correct.
Open the problem → Medium15 minShortest subarray with a sum at least the target
A window that grows on the right until it qualifies, then shrinks on the left while it still does.
Open the problem →Write a solution and run it against the real test table.
Keep going
Maximum of every window
A deque of candidates, kept in falling order, gives the maximum of every window in one pass.
Open the problem → Medium15 minLongest run of distinct characters
The window that grows on the right and shrinks on the left, and the invariant that makes it correct.
Open the problem → Medium15 minShortest subarray with a sum at least the target
A window that grows on the right until it qualifies, then shrinks on the left while it still does.
Open the problem → Sliding windowThe pattern behind it
A window that grows on the right, shrinks on the left, and holds an invariant at all times.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.