80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""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")
|