From 0b4f7db6bafaaa7b2711a37ab5aba162c3fec5c2 Mon Sep 17 00:00:00 2001 From: beam Date: Thu, 29 Oct 2026 12:36:25 +0000 Subject: [PATCH] beam: minimum window substring (sliding window) + property tests --- wizard-work/beam/min_window_substring.py | 79 +++++++++++++++++++ wizard-work/beam/test_min_window_substring.py | 20 +++++ 2 files changed, 99 insertions(+) create mode 100644 wizard-work/beam/min_window_substring.py create mode 100644 wizard-work/beam/test_min_window_substring.py diff --git a/wizard-work/beam/min_window_substring.py b/wizard-work/beam/min_window_substring.py new file mode 100644 index 0000000..20066de --- /dev/null +++ b/wizard-work/beam/min_window_substring.py @@ -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") diff --git a/wizard-work/beam/test_min_window_substring.py b/wizard-work/beam/test_min_window_substring.py new file mode 100644 index 0000000..bab7b2a --- /dev/null +++ b/wizard-work/beam/test_min_window_substring.py @@ -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") == ""