beam: weighted interval scheduling DP with randomized brute-force verification

This commit is contained in:
beam committed 2026-10-28 09:56:41 +00:00
1 parent 24a45c1453
commit 49d21961bd
1 file changed
+88
@@ -0,0 +1,88 @@
"""Weighted Interval Scheduling via dynamic programming.
Given intervals (start, end, weight), choose a subset of mutually
non-overlapping intervals maximizing total weight. Intervals overlap when
their time ranges intersect with positive length; touching endpoints
([a,b] and [b,c]) are allowed to coexist.
Algorithm:
sort intervals by end time
p[i] = largest index j < i with end_j <= start_i (predecessor)
OPT[i] = max(OPT[i-1], weight_i + OPT[p[i]])
answer OPT[n]
Predecessor search uses binary search on sorted end-times: O(n log n).
"""
from __future__ import annotations
import bisect
from typing import List, Tuple
Interval = Tuple[int, int, int] # (start, end, weight)
def weighted_interval_scheduling(intervals: List[Interval]) -> int:
if not intervals:
return 0
# sort by end time; keep original weights
iv = sorted(intervals, key=lambda x: x[1])
n = len(iv)
ends = [iv[i][1] for i in range(n)]
# dp[i] = best using first i intervals (1-indexed); dp[0]=0
dp = [0] * (n + 1)
for i in range(1, n + 1):
s, e, w = iv[i - 1]
# predecessor: largest j (0..i-1) with ends[j-1] <= s -> index j in 1-based terms
# search in ends[0:i-1]
j = bisect.bisect_right(ends, s, 0, i - 1) # count of ends <= s among first i-1
# j is count => predecessor dp index j
take = w + dp[j]
skip = dp[i - 1]
dp[i] = take if take > skip else skip
return dp[n]
def brute_force(intervals: List[Interval]) -> int:
"""Exhaustive enumeration for verification on small inputs."""
n = len(intervals)
best = 0
for mask in range(1 << n):
chosen = [intervals[k] for k in range(n) if mask & (1 << k)]
ok = True
for a in range(len(chosen)):
for b in range(a + 1, len(chosen)):
# overlap with positive length
s1, e1, _ = chosen[a]
s2, e2, _ = chosen[b]
lo = max(s1, s2)
hi = min(e1, e2)
if hi - lo > 0:
ok = False
break
if not ok:
break
if ok:
best = max(best, sum(w for _, _, w in chosen))
return best
if __name__ == "__main__":
# self-test with randomized comparison
import random
random.seed(42)
for trial in range(2000):
n = random.randint(0, 8)
ivs = []
for _ in range(n):
s = random.randint(0, 6)
length = random.randint(1, 4)
e = s + length
w = random.randint(1, 9)
ivs.append((s, e, w))
a = weighted_interval_scheduling(ivs)
b = brute_force(ivs)
if a != b:
raise SystemExit(f"MISMATCH trial={trial} got={a} exp={b} ivs={ivs}")
print("OK: 2000 randomized trials passed")
print("example:", weighted_interval_scheduling([(1, 3, 5), (2, 5, 6), (4, 6, 5), (6, 7, 4)]))