Minimum covering window
Use it when the question asks for the shortest stretch that contains every required value, or each value at least a given number of times. Signal words: shortest, smallest, contains every, covers all, at least once, at least as many times.
the whole pattern cardfold it
State a need map of the required counts, a counts map for the window, and the missing counter, which starts at the number of required copies.
Grow the value at right joins. If its count is now at or below its need, missing goes down by 1.
Shrink and record while missing is 0, record the window if it is strictly shorter, then the value at left leaves. If its count is now below its need, missing goes up by 1.
No cover if missing never reaches 0, return a sentinel such as [-1, -1].
Cost each value joins once and leaves at most once, so the pass is O(n + m).
In this lesson you learn to find the shortest stretch that contains every required value, each as many times as required. You keep the counts map and the need map from earlier lessons, add one counter, and let the window grow and shrink.
1. Scenario: the shortest sponsored block#
A music streaming app sells sponsored blocks on its radio channels. A block is a stretch of consecutive plays in a channel's schedule. Your team lead brings the sponsors' requirement.
Your team lead says: Each sponsor names tracks that must play inside its block, and some must play twice. Sponsors pay by length, so find the shortest block.
If two blocks are equally short, take the earlier one. If no block works, say so.
The planner checks every campaign against every channel each night, and it must finish within one hour.
Here is one short test schedule of track ids: [2, 3, 2, 5, 5, 2, 8, 7, 5, 1]. The campaign needs track 5, the sponsor's jingle, twice. It also needs tracks 7 and 2 once each, so the required list is [5, 7, 5, 2].
Order does not matter, but repeats do. A block may also hold other tracks.
Picture a wide photo of a long bookshelf. You want to crop it to the narrowest strip that still shows every book on a list. The list asks for two copies of one atlas and one copy of each of two novels.
Start with a thin frame at the left end. Drag its right side along the shelf until every book on the list is inside.
Then drag its left side in, one book at a time, for as long as the list stays complete. Note the width each time. When a listed book falls out, go back to dragging the right side.
The list is the need map. The number of listed copies still outside the frame is the missing counter. Dragging the left side in while the list stays complete is the shrink.
Cropping a photo of a long shelf: stretch the right edge until everything required is inside, then pull the left edge in.
2. Struggle: solve it by hand first#
Before you write code, solve the test schedule by hand. It takes three minutes, and it shows you where the work goes.
Solve it by hand: The test schedule by hand: the shortest block with every sponsored track. The hand-solve sheet loads in your browser.
You work through it on the whiteboard. You write one row for each start. From each start, you extend the block until it holds two 5s, one 7 and one 2.
Input: the schedule [2, 3, 2, 5, 5, 2, 8, 7, 5, 1] and the campaign [5, 7, 5, 2].
| Start | Shortest block from this start | Length | Plays read |
|---|---|---|---|
| 0 | 2, 3, 2, 5, 5, 2, 8, 7 (to index 7) | 8 | 8 |
| 1 | 3, 2, 5, 5, 2, 8, 7 (to index 7) | 7 | 7 |
| 2 | 2, 5, 5, 2, 8, 7 (to index 7) | 6 | 6 |
| 3 | 5, 5, 2, 8, 7 (to index 7) | 5 | 5 |
| 4 | 5, 2, 8, 7, 5 (to index 8) | 5 | 5 |
| 5 | none: only one 5 is left | none | 5 |
| 6 | none | none | 4 |
| 7 | none | none | 3 |
| 8 | none | none | 2 |
| 9 | none | none | 1 |
Two blocks tie at 5 plays: 3 to 7 and 4 to 8. Your team lead wants the earlier one, so the answer is plays 3 to 7. In all, You read 46 plays.
Look at starts 0 to 3. All four stop at index 7, and each one reads the same plays again. Remember this, because you will use it soon.
The first attempt#
You turn the hand method into code.
Start with a correct version. From every start, extend the block until its counts cover the campaign.
def covers(counts, need):
for track, copies in need.items(): # every required track needs enough copies
if counts.get(track, 0) < copies:
return False
return True
def shortest_block(schedule, required):
need = {}
for track in required:
need[track] = need.get(track, 0) + 1
best = [-1, -1]
for start in range(len(schedule)):
counts = {}
for end in range(start, len(schedule)): # extend this block one play at a time
counts[schedule[end]] = counts.get(schedule[end], 0) + 1
if covers(counts, need):
if best[0] == -1 or end - start < best[1] - best[0]:
best = [start, end]
break
return bestThis is correct, and it keeps the earlier block on a tie. For the test schedule it reads 46 plays, which takes no time at all. The team keeps it as the reference: every faster version must give the same answers.
How much work is it in general? The worst case is a channel that never plays one of the sponsored tracks. Then no block covers, and every start reads to the end. With n plays, that is n + (n − 1) + ... + 1 = n(n + 1) ÷ 2 reads, and a map check after each one.
The scale check#
Now check the real planner.
Each night the planner checks 50 campaigns against the next 7 days of all 10,000 channels. Most channels never play a campaign's tracks.
Here is the count for one night:
- A track lasts about 3 minutes, so 7 days hold 7 × 24 × 60 ÷ 3 = 3,360 plays per channel.
- On a channel that never plays one of the campaign's tracks, every start reads to the end: 3,360 × 3,361 ÷ 2 = 5,646,480 reads.
- Across 10,000 channels and 50 campaigns: about 2.82 × 10^12 reads, before any map checks.
Predict the cost
Q A computer does about 10^8 simple operations a second. About how long does the first attempt need for one night?
Answer & explanation
Answer: C
2.82 × 10^12 ÷ 10^8 = 28,232 seconds, which is about 7.8 hours. The planner must finish within one hour, and the map checks add more work on top.
The first attempt is correct, but at this size it misses the deadline by hours. Now the team looks for the work it repeats.
3. See: grow until it covers, then shrink#
Can we do better?#
Look at row 0 of the hand trace.
Block 0 to 7 already covers. Why throw it away and read plays 1 to 7 again for start 1? Can we do better?
The block from start 1 needs only plays that block 0 to 7 already holds. So keep one window and never read a play twice.
Two facts make this safe. Adding plays to a block never removes a track, so a covering block stays covering when it grows. Removing plays can only lose tracks, so a block that does not cover still fails when it shrinks.
So the window has two moves. Grow right until it covers. Then shrink from the left while it still covers, and record each covering window on the way.
A window that holds every required value, each as many times as required, is a covering window. The shortest one is the minimum covering window.
Grow right until the window covers every required copy, then shrink from the left while it still covers.
The missing counter#
To know whether the window covers, the first attempt checks the whole need map after every play. You can keep one number instead.
You need three things. The need map counts the required list: here 5 is needed twice, and 7 and 2 once. The counts map counts the window, as in Windows with counts.
The third is missing, the number of required copies the window still lacks. This number is the missing counter, and it starts at the length of the required list, 4.
You never recompute it. It changes only at the edge of a need:
- A value joins and its count rises to at or below its need: it fills a gap, so missing goes down by 1.
- A value joins and its count rises above its need: it is a surplus copy, so missing stays the same.
- A value leaves and its count falls below its need: it opens a gap, so missing goes up by 1.
- Any other change, and any value that is not required, leaves missing as it is.
The window covers exactly when missing is 0. Compare the matched counter from Windows with counts. It counted values at exactly their need, because a reordering allows no extras. A cover allows extras, so here you count the copies that are still absent.
The test schedule, one window#
Watch missing on the test schedule. Track 2 joins at index 0, and missing drops from 4 to 3. Track 2 joins again at index 2, but its need is 1, so this surplus copy changes nothing.
The two 5s bring missing to 1. Track 7 joins at index 7, and missing reaches 0: block 0 to 7 covers.
Now the shrink. Record 0 to 7, and index 0 leaves. It is track 2, but another 2 is still inside, so missing stays 0.
Record 1 to 7, and track 3 leaves. Record 2 to 7, and the second 2 leaves; one 2 is still inside.
Record 3 to 7, length 5, and track 5 leaves. Now only one 5 is inside, below its need of 2, so missing rises to 1 and the shrink stops at left = 4.
When the 5 at index 8 joins, block 4 to 8 covers in 5 plays too. It is not shorter, so the best stays 3 to 7.
Missing counts the required copies still absent: a join lowers it only below the need, and a leave raises it when below.
Where the record goes#
In the longest window of At most K, you recorded after the shrink, when the window was valid again. Here it is the other way round. After the shrink loop, missing is 1, so the window no longer covers.
The window is valid only inside the loop. So record first, then let the value at left leave. This is the same rule as the shortest window in the first lesson of this module.
Why is this fast? Each value joins once, when right reaches it, and leaves at most once, when left passes it. Building the need map reads the m required values once. So the pass is O(n + m), and the loop inside a loop does not make it O(n^2).
Race: First attempt against the covering window, campaign 5, 7, 5, 2. The operation counter loads in your browser.
The race counts the plays each version reads on seeded random schedules. Track 7 plays about once in 100 plays. At 100,000 plays, the first attempt reads 13,548,824 plays. The covering window makes 199,924 count updates: one per join and one per leave.
Here is a new schedule, [4, 9, 4, 4, 6, 1, 4, 1], with the required list [4, 6, 4]. Value 4 is needed twice and value 6 once, so missing starts at 3. The player stops at five steps and asks you to predict.
Dry run: Predict missing, left and the best window. The step-by-step player loads in your browser.
Reading without the player? Cover the missing, left and Best columns and predict them row by row.
| Right | Joins | Count after joining | missing after joining | Recorded, then leaves | Left after | Best |
|---|---|---|---|---|---|---|
| 0 | 4 | 1 | 2 | none | 0 | none |
| 1 | 9 | 1 | 2 | none | 0 | none |
| 2 | 4 | 2 | 1 | none | 0 | none |
| 3 | 4 | 3 | 1 | none | 0 | none |
| 4 | 6 | 1 | 0 | 0 to 4, 1 to 4, 2 to 4; then 4, 9, 4 leave | 3 | 2 to 4 |
| 5 | 1 | 1 | 1 | none | 3 | 2 to 4 |
| 6 | 4 | 2 | 0 | 3 to 6; then 4 leaves | 4 | 2 to 4 |
| 7 | 1 | 2 | 1 | none | 4 | 2 to 4 |
Look at right = 3. A third 4 joins, but 4 is needed only twice, so missing stays 1. At right = 4, three values leave in one shrink, and the third one opens a gap. The shortest cover is [2, 4]: the values 4, 4, 6.
4. Shape: the pattern#
Here is the rule that keeps this correct. Every line of the template below exists to protect it.
The counts map holds the values from left to right, missing counts the required copies they lack, and the shrink reaches the shortest cover ending at right.
Why can left never move back? Say the window from left to right covers. Every later window with the same left is longer, so it can never beat the one you have recorded. Once you record it, the value at left may leave for good.
If the window does not cover, no shorter window that ends at right covers either. So the only useful move is to grow.
Why does the earliest window win a tie? Windows are recorded in order of their right edge. Two windows of the same length that end in order also start in order. So the template replaces the best only when a window is strictly shorter.
Use it when the question asks for the shortest stretch that contains every required value, or each value at least a given number of times. Signal words: shortest, smallest, contains every, covers all, at least once, at least as many times.
State: a need map of the required counts, a counts map for the window, and the missing counter, which starts at the number of required copies.
Grow: the value at right joins. If its count is now at or below its need, missing goes down by 1.
Shrink and record: while missing is 0, record the window if it is strictly shorter, then the value at left leaves. If its count is now below its need, missing goes up by 1.
No cover: if missing never reaches 0, return a sentinel such as [-1, -1].
Cost: each value joins once and leaves at most once, so the pass is O(n + m).
The template: the shortest covering window#
The template returns [start, end] of the earliest shortest window that covers required, or [-1, -1] if no window does.
def min_cover(values, required):
# [1] Count what is required; the empty window lacks every copy.
need = {}
for value in required:
need[value] = need.get(value, 0) + 1
missing = len(required)
window = {}
left = 0
best_start = -1
best_end = -1
for right in range(len(values)):
# [2] The value at right joins; missing drops only while its count is at or below its need.
joining = values[right]
window[joining] = window.get(joining, 0) + 1
if window[joining] <= need.get(joining, 0):
missing -= 1 # it fills a gap; a surplus copy fills none
# [3] While the window covers, record it if shorter, then the value at left leaves.
while missing == 0:
if best_start == -1 or right - left < best_end - best_start:
best_start = left # strictly shorter only, so the earliest of equal windows stays
best_end = right
leaving = values[left]
window[leaving] -= 1
if window[leaving] < need.get(leaving, 0):
missing += 1 # the window now lacks a copy it needs
left += 1
# [4] Report the shortest covering window, or [-1, -1] if none covers.
return [best_start, best_end]public int[] minCover(int[] values, int[] required) {
// [1] Count what is required; the empty window lacks every copy.
Map<Integer, Integer> need = new HashMap<>();
for (int value : required) {
need.put(value, need.getOrDefault(value, 0) + 1);
}
int missing = required.length;
Map<Integer, Integer> window = new HashMap<>();
int left = 0;
int bestStart = -1;
int bestEnd = -1;
for (int right = 0; right < values.length; right++) {
// [2] The value at right joins; missing drops only while its count is at or below its need.
int joining = values[right];
int count = window.getOrDefault(joining, 0) + 1;
window.put(joining, count);
if (count <= need.getOrDefault(joining, 0)) {
missing--; // it fills a gap; a surplus copy fills none
}
// [3] While the window covers, record it if shorter, then the value at left leaves.
while (missing == 0) {
if (bestStart == -1 || right - left < bestEnd - bestStart) {
bestStart = left; // strictly shorter only, so the earliest of equal windows stays
bestEnd = right;
}
int leaving = values[left];
int remaining = window.get(leaving) - 1;
window.put(leaving, remaining);
if (remaining < need.getOrDefault(leaving, 0)) {
missing++; // the window now lacks a copy it needs
}
left++;
}
}
// [4] Report the shortest covering window, or [-1, -1] if none covers.
return new int[] {bestStart, bestEnd};
}vector<int> minCover(const vector<int>& values, const vector<int>& required) {
// [1] Count what is required; the empty window lacks every copy.
unordered_map<int, int> need;
for (int value : required) {
need[value]++;
}
int missing = required.size();
unordered_map<int, int> window;
int left = 0;
int bestStart = -1;
int bestEnd = -1;
for (int right = 0; right < (int) values.size(); right++) {
// [2] The value at right joins; missing drops only while its count is at or below its need.
int joining = values[right];
int count = ++window[joining];
auto found = need.find(joining);
if (found != need.end() && count <= found->second) {
missing--; // it fills a gap; a surplus copy fills none
}
// [3] While the window covers, record it if shorter, then the value at left leaves.
while (missing == 0) {
if (bestStart == -1 || right - left < bestEnd - bestStart) {
bestStart = left; // strictly shorter only, so the earliest of equal windows stays
bestEnd = right;
}
int leaving = values[left];
int remaining = --window[leaving];
auto needed = need.find(leaving);
if (needed != need.end() && remaining < needed->second) {
missing++; // the window now lacks a copy it needs
}
left++;
}
}
// [4] Report the shortest covering window, or [-1, -1] if none covers.
return {bestStart, bestEnd};
}function minCover(values, required) {
// [1] Count what is required; the empty window lacks every copy.
const need = new Map();
for (const value of required) {
need.set(value, (need.get(value) || 0) + 1);
}
let missing = required.length;
const window = new Map();
let left = 0;
let bestStart = -1;
let bestEnd = -1;
for (let right = 0; right < values.length; right++) {
// [2] The value at right joins; missing drops only while its count is at or below its need.
const joining = values[right];
const count = (window.get(joining) || 0) + 1;
window.set(joining, count);
if (count <= (need.get(joining) || 0)) {
missing--; // it fills a gap; a surplus copy fills none
}
// [3] While the window covers, record it if shorter, then the value at left leaves.
while (missing === 0) {
if (bestStart === -1 || right - left < bestEnd - bestStart) {
bestStart = left; // strictly shorter only, so the earliest of equal windows stays
bestEnd = right;
}
const leaving = values[left];
const remaining = window.get(leaving) - 1;
window.set(leaving, remaining);
if (remaining < (need.get(leaving) || 0)) {
missing++; // the window now lacks a copy it needs
}
left++;
}
}
// [4] Report the shortest covering window, or [-1, -1] if none covers.
return [bestStart, bestEnd];
}Rebuild the template: put its lines back in order. Loads in your browser.
Two details are worth a second look. Step [2] compares the new count with the need, so a surplus copy and a value that is not required both fail the test and leave missing alone. Step [3] checks the need after lowering the count, so it fires only on the copy that opens a gap.
To return only the length, keep the same loop and return best_end - best_start + 1, or 0 when nothing covers. To cover every distinct value of the input, build the need map from the distinct values, each needed once.
The covering template: need map and missing, join at right, then while missing is zero record and let left leave.
A search engine shows a short snippet under each help-centre result. The snippet should be the shortest run of consecutive words that holds every term of the query. The indexer stores each word as a term id. The article is [31, 5, 14, 8, 8, 27, 14, 31, 5, 27], and the query's terms are [31, 14, 27].
This is the template with required = [31, 14, 27], so missing starts at 3.
| Right | Joins | missing after joining | Recorded, then leaves | Left after | Best |
|---|---|---|---|---|---|
| 0 | 31 | 2 | none | 0 | none |
| 1 | 5 | 2 | none | 0 | none |
| 2 | 14 | 1 | none | 0 | none |
| 3 | 8 | 1 | none | 0 | none |
| 4 | 8 | 1 | none | 0 | none |
| 5 | 27 | 0 | 0 to 5; then 31 leaves | 1 | 0 to 5 |
| 6 | 14 | 1 | none | 1 | 0 to 5 |
| 7 | 31 | 0 | 1 to 7, 2 to 7, 3 to 7, 4 to 7, 5 to 7; then 5, 14, 8, 8, 27 leave | 6 | 5 to 7 |
| 8 | 5 | 1 | none | 6 | 5 to 7 |
| 9 | 27 | 0 | 6 to 9; then 14 leaves | 7 | 5 to 7 |
The snippet is words 5 to 7: 27, 14, 31. Look at right = 7. The 14 at index 2 can leave, because the 14 at index 6 is still inside. Five words leave in one shrink, which only a while loop can do.
Complexity in plain words#
- Time. Building the need map reads the m required values once. Then each of the n values joins once and leaves at most once, and each is one hash map update plus a comparison. That is O(n + m) in all.
- Space. The need map holds one key per distinct required value. The window map in the template holds every distinct value in the window. To keep it at O(m), count only the values that are in the need map.
- No map checks. The missing counter replaces the check of the whole need map after every step. Each join and each leave changes it by at most 1, in O(1) time.
- At planner size. Each channel and campaign needs at most 3,360 joins, 3,360 leaves and 4 need updates: 6,724 in all. Across 10,000 channels and 50 campaigns that is about 3.36 × 10^9 updates: about 34 seconds on one core, instead of 7.8 hours.
5. Stretch: edges, mistakes and variations#
Edge cases#
Real production data brings edge cases.
The schedule [5, 7, 5] never plays track 2, and the campaign is [5, 2]. No block can cover it.
missing drops to 1 and never reaches 0, so the shrink loop never runs. The best stays at [-1, -1], the sentinel for "no block". Make sure your code returns it and does not report a half-covered block.
For [3, 8, 2, 8] with the campaign [8], missing starts at 1. The first 8 joins at index 1, and missing reaches 0.
The loop records window 0 to 1, then 1 to 1, and stops when the 8 leaves. The answer is [1, 1], a block of one play. The later 8 at index 3 also gives a block of one play, but it is not shorter, so the earlier one stays.
The campaign [5, 5, 7] needs the jingle twice, but the schedule [5, 7, 2] plays it once. The answer is [-1, -1].
The same holds when the required list is longer than the schedule, as in [4, 4] with [4, 4, 4]. No special check is needed: missing starts at 3, falls to 1 and stays there.
For [7, 3, 3, 5] with the campaign [5, 7], track 7 is the first play and track 5 the last. missing reaches 0 only at the last index.
The loop records window 0 to 3, track 7 leaves, and missing rises to 1. The answer is [0, 3], the whole schedule. Check that your code can return a window that starts at 0 and ends at n − 1.
For [2, 5, 9, 9, 5, 2] with the campaign [2, 5], blocks 0 to 1 and 4 to 5 both cover in 2 plays. The answer is [0, 1].
The template replaces the best only when right - left < best_end - best_start. With <=, it would report [4, 5], the later block.
The mistake museum#
This version lowers missing every time a required value joins, and raises it every time one leaves, without looking at the need:
if joining in need:
missing -= 1 # bug: a surplus copy fills no gapCounterexample: the test schedule [2, 3, 2, 5, 5, 2, 8, 7, 5, 1] with the campaign [5, 7, 5, 2]. The bug returns [2, 5]. The correct answer is [3, 7].
The second 2 at index 2 takes 1 off missing, although the campaign needs only one 2. When the second 5 joins at index 4, missing reaches 0, but no track 7 has played yet. The block the bug reports, 2, 5, 5, 2, has no track 7 at all.
Counting every joined copy against missing treats a surplus copy as progress, and the window looks complete too early.
This version shrinks first and records once the loop ends, as the longest template does:
shrank = False
while missing == 0:
... # the value at left leaves, as in the template
shrank = True
if shrank and (best_start == -1 or right - left < best_end - best_start):
best_start, best_end = left, right # bug: the window has just lost a required copyCounterexample: the test schedule [2, 3, 2, 5, 5, 2, 8, 7, 5, 1] with the campaign [5, 7, 5, 2]. The bug returns [4, 7]. The correct answer is [3, 7].
The loop ends only after the jingle at index 3 has left. So the bug records 5, 2, 8, 7, which plays the jingle once. Record inside the loop, while the window still covers.
This version shrinks by at most one value per step:
if missing == 0: # bug: this must be a while loop
... # record, then the value at left leavesCounterexample: [5, 8, 5, 2] with the campaign [5, 2]. The bug returns [0, 3]. The correct answer is [2, 3].
When track 2 joins at index 3, block 0 to 3 covers. The if records it, removes the first 5 and moves on. But block 1 to 3 and block 2 to 3 still cover, and the loop never sees them. One join can make several values at the left unnecessary.
This version drops the missing counter. After every join and every leave, it checks each required value against the window's counts:
while all(window.get(value, 0) >= copies for value, copies in need.items()):
... # record, then the value at left leavesIt always returns the right answer, but it is slow when the campaign is large. Counterexample: 100,000 plays from a catalogue of 1,000 tracks, with 200 required tracks. The checks make 29,342,106 lookups. The missing counter needs 191,368 updates for the same window, and in Python it runs in about 0.04 seconds instead of 2.
When the pattern does not apply#
Your team lead brings a request from one sponsor: its tracks must play in order, the jingle first, then track 7, then track 2. Other tracks may sit between them. For the schedule [7, 5, 2, 8, 5, 7, 3, 2], the shortest such block is plays 4 to 7: 5, 7, 3, 2.
The covering window returns [0, 2], the block 7, 5, 2. It holds all three tracks, but in the wrong order. Counts cannot see order, so the missing counter cannot check it.
This is the shortest window that contains a subsequence. It needs a different method, which walks forward to finish a match and then backward to find its latest start (LeetCode 727: Minimum Window Subsequence). And if the plays need not be consecutive at all, there is no window: count the whole schedule instead.
Product variations#
Your team lead brings five product changes. Each one keeps a need map, a counts map and a window that grows and shrinks.
A monitoring system wants the shortest stretch of log lines that shows every code on an incident checklist. The checklist names each code once. For the codes [503, 404, 429, 503, 500, 429] and the checklist [429, 500, 503], the answer is [2, 4].
This is the template with a need of 1 for every code. It is the first practice problem.
A chat app wants the shortest stretch of a group thread in which every member posts at least once. The members are the distinct senders of the whole thread. For the senders [4, 9, 4, 4, 2, 9, 2], the answer is 3: messages 3 to 5, from senders 4, 2 and 9.
Build the need map from the distinct values, each needed once, and return the length. It is the second practice problem.
An online store replays the part of a browsing session where the shopper viewed the wishlist items. An item listed twice needs two views. For the views [11, 30, 11, 52, 30, 11, 52] and the wishlist [11, 11, 52], spans 0 to 3 and 2 to 5 both cover in 4 views, and the answer is [0, 3].
This is the template with real repeats in the need map and an earliest tie. It is the third practice problem.
A parcel-delivery app unloads parcels from the two ends of a truck and wants at least k of each parcel type, unloading as few as possible. For the types [2, 1, 1, 3, 1, 1, 2, 3, 1] with k = 1, the answer is 3: the last three parcels, 2, 3, 1.
The parcels left on the truck form one middle block. So turn it around: find the longest middle block that leaves at least k of each type outside. That block may hold at most the total of each type minus k, which is a longest window like At most K. Here it is parcels 0 to 5, so 9 − 6 = 3 parcels are unloaded.
An engineering platform queues build jobs, and each job is assigned to a shard. Every shard should get an equal share. The team reassigns one block of consecutive jobs and wants the shortest block that makes this possible.
For the shards [1, 1, 2, 1, 1, 0, 0, 1, 1] with 3 shards, each share is 3, and shard 1 holds 6 jobs. So the block must hold at least the 3 surplus jobs of shard 1. The need map is the surplus, and the answer is a block of 4 jobs.
Why this matters in real systems#
6. Say: lock it in#
Explain it back#
Explain it back: write the idea in your own words and get feedback.
In two or three sentences, explain to a teammate how you find the shortest window that covers a required list in one pass. Say when the missing counter changes and where you record the answer. The AI tutor checks your answer against the invariant and the key points of this lesson.
Here is how you could explain it in an interview, now that you have built it.
I keep a need map, a counts map and missing. missing drops only when a count rises to at or below its need.
I grow right until missing is 0. Then, while it stays 0, I record the window and let the value at left leave.
Each value joins once and leaves once, so it is O(n + m). Strictly shorter wins, so ties keep the earliest.
The 60-second interview answer#
The question asks for the shortest stretch that contains every required value, so I would use a minimum covering window. The first attempt extends from every start and checks the counts, which is O(n^2) when no stretch covers.
Instead, I build a need map from the required list and keep a counts map for the window. I also keep missing, the number of required copies the window still lacks. When a value joins and its count is at or below its need, missing drops by one. When a value leaves and its count falls below its need, missing rises by one.
I grow the window on the right until missing is 0. Then, while missing stays 0, I record the window if it is strictly shorter and let the left value leave. Each value joins once and leaves once, so this is O(n + m) time. I would test a missing value, repeated requirements, ties and the whole array.
Checkpoint#
Checkpoint · Spot the pattern and the counter
Q1/4 Which request is a job for the minimum covering window?
Answer & explanation
Answer: B
"Shortest stretch" with "every code at least once" is the signal. The first request is an at-most-K window, the third is a fixed window with counts, and the last is not about a stretch.
Q2/4 Value 6 is needed twice. The window already holds two 6s, and missing is 1. Another 6 joins. What is missing now?
Answer & explanation
Answer: B
The new count is 3, above the need of 2. A surplus copy fills no gap, so missing stays 1 (See: the missing counter).
Q3/4 When does missing go up by 1?
Answer & explanation
Answer: B
missing counts required copies the window lacks. A leave opens a gap only when the count falls below the need. If another copy is still inside, nothing changes (See: the missing counter).
Q4/4 In the shortest covering window, where do you record the window?
Answer & explanation
Answer: B
The window covers only while missing is 0. When the loop ends, missing is 1, so a record there would report a window that lacks a required copy (See: where the record goes; Exhibit 2).
Checkpoint · Running the window
Q1/3 A version shrinks with
ifinstead ofwhile. For[5, 8, 5, 2]with the required list[5, 2], what does it return?Answer & explanation
Answer: A
When 2 joins, block 0 to 3 covers. The
ifrecords it and removes one value, so it never sees block 2 to 3. The correct answer is[2, 3](Exhibit 3).Q2/3 Which statement is the covering invariant?
Answer & explanation
Answer: C
Each join and each leave updates one count and missing together. The shrink records every covering window down to the shortest one that ends at right (Shape: the covering invariant).
Q3/3 What does the template return for
[6, 3, 6]with the required list[6, 6, 6]?Answer & explanation
Answer: B
missing starts at 3 and falls only to 1, because there are two 6s. The shrink loop never runs, so the best stays at the sentinel
[-1, -1](Stretch: required counts above what the schedule holds).
Checkpoint · Ties, cost and limits
Q1/3 For
[4, 1, 7, 7, 1, 4]with the required list[1, 4], blocks 0 to 1 and 4 to 5 both cover in 2. Which does the template return?Answer & explanation
Answer: A
Windows are recorded in order of their end, so the earlier window comes first. A later window of the same length is not strictly shorter, so the best stays
[0, 1](Stretch: two blocks of the same length).Q2/3 The covering window has a while loop inside a for loop. With n values and m required values, what is its time?
Answer & explanation
Answer: B
Building the need map takes m steps. Right moves n times and left moves at most n times in total. The missing counter makes each check O(1) (See: where the record goes; Complexity).
Q3/3 Which request should NOT use the covering window as it stands?
Answer & explanation
Answer: A
Counts cannot see order. A required order makes it a subsequence problem with a different method (LeetCode 727: Minimum Window Subsequence). The other three are covering windows (Stretch: in a given order).
Ten questions on this lesson. Pass with 80% or more to open the gate to the next stretch on the Academy map.
Practice ladder#
| Step | Problem | Product line | Level | What it practises |
|---|---|---|---|---|
| 1 | Shortest log window with every required code | Observe | Medium | The template with distinct codes, earliest tie, no cover |
| 2 | Shortest stretch where every member posts | Chat | Medium | A need map built from the distinct values, returning a length |
| 3 | Shortest browsing span with every wishlist item | Shop | Hard | Repeated requirements, surplus copies and the earliest tie |
Review cards#
Review cards from this lesson.
Five cards from this lesson join your spaced-review deck. The first one comes back tomorrow.
This lesson completes the four patterns of Window Tower: fixed and variable windows, windows with counts, at most K, and the minimum covering window.