beam: minimum window substring (sliding window) + property tests
This commit is contained in:
1 parent
7ff8a904ee
commit
0b4f7db6ba
2 files changed
+99
No files matched your search
@@ -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") == ""
|
||||
Reference in new issue
Block a user