Shortest window holding every kind
Given a string s, return the length of the shortest substring that contains every
distinct character appearing anywhere in s. Return 0 for an empty string.
Examples
| Input | Output | Why |
|---|---|---|
s = "aabcbcdbca" | 4 | "dbca" holds a, b, c and d. |
s = "aaab" | 2 | "ab". |
s = "zzz" | 1 |
Constraints
- 0 ≤ |s| ≤ 10⁵
- s is lowercase English letters
Hints
Hint 1
Count the kinds first: that is the target the window must reach.
Hint 2
Once a window covers every kind, so does any window containing it. You want the smallest, so shrink from the left while it still covers.
Hint 3
Record the length inside the shrinking loop, before each removal, not after it.
Where people lose marks
- Recording the length after the shrinking loop, when the window has just stopped covering.
- Counting the target kinds from the window instead of from the whole string.
Dry run on "aabcbcdbca"
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 / 11sa0a1b2c3b4c5d6b7c8a9need4
The string holds 4 kinds of letter: a, b, c, d. A window must contain all of them.
-
Step 2 / 11sa0lo/hia1b2c3b4c5d6b7c8a9have1best—
'a' enters; the window covers 1 of 4.
-
Step 3 / 11sa0loa1hib2c3b4c5d6b7c8a9have1best—
'a' enters; the window covers 1 of 4.
-
Step 4 / 11sa0loa1b2hic3b4c5d6b7c8a9have2best—
'b' enters; the window covers 2 of 4.
-
Step 5 / 11sa0loa1b2c3hib4c5d6b7c8a9have3best—
'c' enters; the window covers 3 of 4.
-
Step 6 / 11sa0loa1b2c3b4hic5d6b7c8a9have3best—
'b' enters; the window covers 3 of 4.
-
Step 7 / 11sa0loa1b2c3b4c5hid6b7c8a9have3best—
'c' enters; the window covers 3 of 4.
-
Step 8 / 11sa0a1b2loc3b4c5d6hib7c8a9have3best6
'd' enters; the window covers 4 of 4. It covers everything, so record 7, then 6 while dropping from the left; it stops covering when lo reaches 2. Best 6.
-
Step 9 / 11sa0a1b2loc3b4c5d6b7hic8a9have3best6
'b' enters; the window covers 3 of 4.
-
Step 10 / 11sa0a1b2loc3b4c5d6b7c8hia9have3best6
'c' enters; the window covers 3 of 4.
-
Step 11 / 11sa0a1b2c3b4c5d6b7loc8a9hihave3best4
'a' enters; the window covers 4 of 4. It covers everything, so record 8, then 7, then 6, then 5, then 4 while dropping from the left; it stops covering when lo reaches 7. Best 4.
How to think about it
Covering every kind is the opposite of the longest-window properties: it survives growing, not shrinking. So the loop is inverted. Grow on the right until the window covers every kind; then, while it still covers, record its length and drop a letter from the left. When it stops covering, grow again.
The left end still never moves backwards. When the shrinking stops at lo, the window from
lo − 1 to hi covered and its length has been recorded. Any later window that starts
before lo contains it, so it is longer and cannot improve the answer. Each pointer moves at most n
times, so the scan is linear. The state is
a count per letter and the number of letters with a nonzero count.
Complexity. Time O(n) · Space O(1), 26 counts
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def shortest_all_kinds(s):
need = len(set(s))
count = [0] * 26
have = lo = 0
best = len(s)
for hi, c in enumerate(s):
count[ord(c) - 97] += 1
if count[ord(c) - 97] == 1:
have += 1
while have == need: # valid: record, then try a shorter one
best = min(best, hi - lo + 1)
count[ord(s[lo]) - 97] -= 1
if count[ord(s[lo]) - 97] == 0:
have -= 1
lo += 1
return best#include <string>
#include <array>
#include <algorithm>
int solve(const std::string& s) {
std::array<int, 26> seen{}, count{};
int need = 0;
for (char c : s) if (seen[c - 'a']++ == 0) ++need;
int have = 0, lo = 0, best = (int)s.size();
for (int hi = 0; hi < (int)s.size(); ++hi) {
if (++count[s[hi] - 'a'] == 1) ++have;
while (have == need) { // valid: record, then try a shorter one
best = std::min(best, hi - lo + 1);
if (--count[s[lo] - 'a'] == 0) --have;
++lo;
}
}
return best;
}class Solution {
public int shortestAllKinds(String s) {
int[] seen = new int[26], count = new int[26];
int need = 0;
for (char c : s.toCharArray()) if (seen[c - 'a']++ == 0) need++;
int have = 0, lo = 0, best = s.length();
for (int hi = 0; hi < s.length(); hi++) {
if (++count[s.charAt(hi) - 'a'] == 1) have++;
while (have == need) { // valid: record, then try a shorter one
best = Math.min(best, hi - lo + 1);
if (--count[s.charAt(lo) - 'a'] == 0) have--;
lo++;
}
}
return best;
}
}function shortestAllKinds(s) {
const need = new Set(s).size, count = new Array(26).fill(0);
let have = 0, lo = 0, best = s.length;
for (let hi = 0; hi < s.length; hi++) {
if (++count[s.charCodeAt(hi) - 97] === 1) have++;
while (have === need) { // valid: record, then try a shorter one
best = Math.min(best, hi - lo + 1);
if (--count[s.charCodeAt(lo) - 97] === 0) have--;
lo++;
}
}
return best;
}fn solve(s: &str) -> usize {
let b = s.as_bytes();
let mut seen = [false; 26];
let mut need = 0;
for &c in b { if !seen[(c - b'a') as usize] { seen[(c - b'a') as usize] = true; need += 1; } }
let mut count = [0usize; 26];
let (mut have, mut lo, mut best) = (0, 0usize, b.len());
for hi in 0..b.len() {
count[(b[hi] - b'a') as usize] += 1;
if count[(b[hi] - b'a') as usize] == 1 { have += 1; }
while have == need && need > 0 { // valid: record, then try a shorter one
best = best.min(hi + 1 - lo);
count[(b[lo] - b'a') as usize] -= 1;
if count[(b[lo] - b'a') as usize] == 0 { have -= 1; }
lo += 1;
}
}
best
}import qualified Data.Map.Strict as M
import Data.List (nub)
-- Fold over the letters carrying (counts, letters from the window's left end, window length, best).
-- After each letter enters, shrink while the window still covers every kind, recording each length.
solve :: String -> Int
solve s = best
where
need = length (nub s)
(_, _, _, best) = foldl step (M.empty, s, 0, length s) s
step (m, win, n, b) c = shrink (M.insertWith (+) c 1 m) win (n + 1) b
shrink m win n b
| M.size m == need, (x:xs) <- win =
shrink (M.update (\k -> if k == 1 then Nothing else Just (k - 1)) x m) xs (n - 1) (min b n)
| otherwise = (m, win, n, b)
Variants to try
- Cover the letters of a second string, with multiplicities: that is the minimum window cover problem in this module.
- Return the window itself: remember lo when the length improves.
Next problems
Shortest 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 → Hard30 minShortest window containing every letter
Count what the window still needs; extend on the right until nothing is missing, then cut from the left.
Open the problem → Easy10 minBest total over k consecutive days
A window of fixed width slides one place at a time: add the element that enters, subtract the one that leaves.
Open the problem →Write a solution and run it against the real test table.
Keep going
Shortest 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 → Hard30 minShortest window containing every letter
Count what the window still needs; extend on the right until nothing is missing, then cut from the left.
Open the problem → Easy10 minBest total over k consecutive days
A window of fixed width slides one place at a time: add the element that enters, subtract the one that leaves.
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.