beam: minimum window substring (sliding window) + property tests

This commit is contained in:
beam authored and ledger committed 2026-10-29 12:36:27 +00:00
1 parent 7ff8a904ee
commit 0b4f7db6ba
2 files changed
+99

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
"""Minimum window substring (LeetCode 76).
Given strings s and t, return the smallest substring of s such that every
character in t (with multiplicity) occurs in it. Empty string if none.
Algorithm: sliding window with two pointers and a "need" counter.
Invariant: window [left, right) is the smallest window ending at right-1 that
contains t, tracked via `formed` count of satisfied character requirements.
We expand right until all requirements met, then shrink left as far as possible
while still meeting them, recording the best.
"""
from collections import Counter
def min_window(s: str, t: str) -> str:
if not t or not s or len(t) > len(s):
return ""
need = Counter(t)
required = len(need) # distinct chars needed
window = {}
formed = 0
left = 0
best_len = float("inf")
best_start = 0
for right, ch in enumerate(s):
window[ch] = window.get(ch, 0) + 1
if ch in need and window[ch] == need[ch]:
formed += 1
while left <= right and formed == required:
cur_len = right - left + 1
if cur_len < best_len:
best_len = cur_len
best_start = left
lc = s[left]
window[lc] -= 1
if lc in need and window[lc] < need[lc]:
formed -= 1
left += 1
if best_len == float("inf"):
return ""
return s[best_start:best_start + best_len]
def _brute(s, t):
from collections import Counter
nt = Counter(t)
best = None
n = len(s)
for i in range(n):
for j in range(i + len(t), n + 1):
w = s[i:j]
if all(Counter(w)[c] >= nt[c] for c in nt):
if best is None or len(w) < len(best):
best = w
break
return best or ""
if __name__ == "__main__":
import random, string
# property-based randomized check against brute force
rng = random.Random(42)
for _ in range(2000):
n = rng.randint(0, 12)
s = "".join(rng.choice("abcABC") for _ in range(n))
m = rng.randint(0, 6)
t = "".join(rng.choice("abcABC") for _ in range(m))
a = min_window(s, t)
b = _brute(s, t)
# compare lengths (ties: any minimal window acceptable, but brute returns first;
# we only assert length equality and validity)
assert len(a) == len(b), (s, t, a, b)
# validity
if a:
from collections import Counter
cnt = Counter(a)
nt = Counter(t)
assert all(cnt[c] >= nt[c] for c in nt)
print("property tests passed")
@@ -0,0 +1,20 @@
import sys, os
sys.path.insert(0, os.path.dirname(__file__))
from min_window_substring import min_window
def test_basic():
assert min_window("ADOBECODEBANC", "ABC") == "BANC"
def test_a():
assert min_window("a", "a") == "a"
def test_no():
assert min_window("a", "aa") == ""
def test_empty_t():
assert min_window("abc", "") == ""
def test_empty_s():
assert min_window("", "a") == ""
def test_dup_t():
assert min_window("aa", "aa") == "aa"
def test_unicode():
assert min_window("a\u00e9b\u00e9c", "\u00e9\u00e9") == "\u00e9b\u00e9"
def test_single_char_missing():
assert min_window("abc", "d") == ""