Minimum covering window: track unmet demandLESSON 2.06 · 6 OF 43 IN CHAPTER
PART A / Coding problems and trade-offs
Step 32 of 252
LESSON 2.06 · 6 OF 43 IN CHAPTERTry it, then open the solution

Minimum covering window: track unmet demand

Constructed practice problem; no company attribution. Prerequisites: longest unique window and anagram counts.

Candidate brief

THE PROBLEM

A log inspector receives a text and a multiset of required marker characters. Highlight the shortest contiguous slice containing every requested copy, allowing extra characters. Explain how duplicate requirements change your state before coding.

Write this:

def minimum_covering_window(text, required):
    ...
Contract Decision
Input Python strings text and required; exact code points
Output Shortest half-open (start, end), earliest start on ties; None if impossible
Boundaries Empty requirement returns (0, 0); surplus copies are allowed
Invalid input Either non-string argument raises ValueError
Excluded Reordering characters or matching tokens across noncontiguous positions
Optional refresher · the underlying tool

A covering window may contain characters the request does not need. Represent the outstanding demand as counts, not a set:

from collections import Counter
need = Counter("AAB")  # A is required twice
print(need["A"])       # 2

With text "AAAB" and requirement "AAB", the shortest cover is "AAB" at the end. Expanding earns characters; shrinking is safe only while every required count remains satisfied.

A design choice worth saying aloud

Keep the requested counts separate from the mutable remaining_by_character. The latter answers how many more of each character the current window owes; a negative count means surplus, not failure. A single missing total can tell you when to shrink, while the count map tells you which removal would break coverage.

What the interviewer expects

The interviewer gives you the scenario and the contract above. Explain what a successful call returns, walk through one example below, and name what your state means before choosing a data structure.

Done means: Shortest half-open (start, end), earliest start on ties; None if impossible.

Now predict each output before looking at the reference; invalid input should leave any existing state unchanged unless the contract says otherwise.

Test-case scenarios to settle before coding

01 · Representative

Input / starting state
"ABAAC", required "AAC"
Expected result
(2, 5) for "AAC"

What it is testing: Required multiplicities drive validity.

02 · Empty requirement

Input / starting state
any text, required ""
Expected result
(0, 0)

What it is testing: The empty need is already satisfied.

03 · Impossible multiplicity

Input / starting state
"ab", required "aa"
Expected result
None

What it is testing: Presence without enough copies is insufficient.

04 · Surplus

Input / starting state
"AAABC", required "AC"
Expected result
(2,5) for "ABC"

What it is testing: Extra required characters must not inflate unmet demand.

05 · Tie

Input / starting state
"ABXAB", required "AB"
Expected result
(0,2) for the first "AB"

What it is testing: State the deterministic result before coding.

06 · Invalid

Input / starting state
either argument is not a string
Expected result
ValueError

What it is testing: Validation precedes scanning.

For each case, show which branch or state change produces that result.

minimum_covering_window("ABAAC", "AAC") == (2, 5) highlights "AAC". minimum_covering_window("ab", "aa") is None; minimum_covering_window("", "") == (0, 0). minimum_covering_window("abc", None) raises ValueError.

Before opening the explanation, restate the contract, trace the smallest useful example, implement a baseline, and identify the repeated work. Then implement your improvement independently and derive tests from the contract. Say what your state means before saying which data structure stores it.

Worked lesson, changed requirements, and reference

Make validity a cheap question

A baseline enumerates each start and extends a count map until all requirements are covered. Repeating that scan costs O(n²) updates plus validity checks. A set is insufficient: required AAC needs two As. The reusable method is to name the expensive question, then maintain its answer incrementally. Let need[c] be required count minus current-window count, and missing be the number of required occurrences still absent. Negative need means harmless surplus.

Read / window need A need C missing Action
initially 2 1 3 expand
A 1 1 2 expand
AB 1 1 2 ignore irrelevant B
ABAA -1 1 1 extra A is surplus
ABAAC -1 0 0 valid; start shrinking
BAAC, then AAC 0 0 0 improve to [2, 5)
AC after removing A 1 0 1 invalid; expand again

When adding a required character, decrement missing only if its prior need was positive, then decrement need. When removing one, increment need first; if it becomes positive, increment missing. This update order distinguishes a required copy from surplus. Non-required characters need no map entries.

While missing is zero, record the candidate and advance left. This visits the shortest valid window ending at each right edge. A start discarded during shrinking cannot improve at a later right edge: its window would only get longer. Both boundaries move forward at most n times, giving expected O(n + m) time and O(k) auxiliary space for k required code points. The returned indices use O(1) space. Strictly shorter updates preserve the earliest tie. This proof depends on coverage being preserved by expansion; arbitrary window predicates do not automatically support two pointers.

Follow-up 1: matches must appear in required order

Predict what breaks when required="AC" means a subsequence in that order. Inventory sees CA as valid although the sequence is impossible.

Text prefix Coverage interpretation Ordered interpretation
C A still missing no A has started a match
CA complete inventory A starts a future match
CAC complete inventory AC at [1, 3) completes

Use state for progress through the required sequence, such as the latest viable start for each matched prefix. Update matching states backward per text character so one occurrence cannot advance several repeated required positions. An O(nm) dynamic program is a defensible baseline; the count invariant is gone.

Follow-up 2: several requirement sets share one text

Diagram: Follow-up 2: several requirement sets share one text

Independent demand states remain correct but cost O(qn + total demand size). Do not claim one universal left boundary: different queries become valid at different positions. A senior candidate derives the exact update order and tests multiplicity and ties. A lead candidate sets query-count and retained-text budgets and measures whether shared preprocessing is justified by the workload.

Run and check

From the repository root:

cd curriculum/01-code/02-data-structures-algorithms/problems/05-minimum-covering-window
python -m unittest -v test_solution.py

Reference implementation (download file, source below) · Contract and oracle tests (download file, source below). Read the tests after your attempt. A green reference suite verifies the supplied implementation; it does not demonstrate independent transfer. Reimplement one follow-up with the reference closed and explain which old invariant no longer holds.

Reference implementation · solution.py
def minimum_covering_window(text, required):
    if not isinstance(text, str) or not isinstance(required, str):
        raise ValueError("arguments must be strings")
    if not required:
        return 0, 0
    need = {}
    for char in required:
        need[char] = need.get(char, 0) + 1
    missing = len(required)
    left = 0
    best = None
    for right, char in enumerate(text):
        if char in need:
            if need[char] > 0:
                missing -= 1
            need[char] -= 1
        while missing == 0:
            if best is None or right + 1 - left < best[1] - best[0]:
                best = left, right + 1
            departing = text[left]
            if departing in need:
                need[departing] += 1
                if need[departing] > 0:
                    missing += 1
            left += 1
    return best
Contract and oracle tests · test_solution.py
import itertools
import unittest
from collections import Counter
from solution import minimum_covering_window

class CoverTests(unittest.TestCase):
    def test_examples_and_ties(self):
        self.assertEqual(minimum_covering_window("ABAAC", "AAC"), (2, 5))
        self.assertEqual(minimum_covering_window("abxba", "ab"), (0, 2))
        self.assertIsNone(minimum_covering_window("ab", "aa"))
        self.assertEqual(minimum_covering_window("", ""), (0, 0))
        self.assertEqual(minimum_covering_window("🙂x🙂é", "🙂é"), (2, 4))
        with self.assertRaises(ValueError):
            minimum_covering_window("abc", None)

    def test_all_substrings_oracle(self):
        for n in range(6):
            for chars in itertools.product("ab", repeat=n):
                text = "".join(chars)
                for required in ["", "a", "b", "aa", "ab", "aab"]:
                    candidates = [(i, j) for i in range(n + 1) for j in range(i, n + 1)
                                  if not (Counter(required) - Counter(text[i:j]))]
                    expected = min(candidates, key=lambda pair: (pair[1] - pair[0], pair[0])) if candidates else None
                    self.assertEqual(minimum_covering_window(text, required), expected)

if __name__ == "__main__":
    unittest.main()