return max(aleft, bleft) # the k-th smallest
The invariant. A cut splits both arrays: take i elements from A and j = k − i from B into the left half, so leftA + leftB == k. The cut is a valid k-th partition iff every left element is ≤ every right element:
max(A[i-1], B[j-1]) <= min(A[i], B[j])
Since each array is individually sorted, this reduces to exactly two boundary checks, and both must be non-strict (<=):
A[i-1] <= B[j] and B[j-1] <= A[i] (with ±inf sentinels at the edges)
The bug. A mixed <=/< predicate, e.g. A[i-1] < B[j] and B[j-1] <= A[i], rejects a cut when a duplicate value straddles the boundary (A[i-1] == B[j]). But that cut is genuinely valid — the k-th element can legitimately sit on either side of the boundary. The search then pushes i the wrong way. Because j = k − i must stay in [0, n], the feasible window i ∈ [max(0, k−n), min(m, k)] shrinks to a handful of values when k is near an array end; the binary search exhausts the window without ever accepting the true cut, and falls back on a partition whose left maximum exceeds the right minimum, returning an element that is provably not the k-th smallest.
The fix. Test both sides with <=, and clamp the binary-search window so the invariant leftA + leftB == k is satisfiable for every k ∈ [1, m+n]:
import math
def kth_smallest(a, b, k):
"""k-th smallest (1-indexed) of two sorted arrays, O(log(min(m, n)))."""
m, n = len(a), len(b)
if m > n: # binary-search the shorter array
a, b = b, a
m, n = n, m
lo, hi = max(0, k - n), min(m, k) # keeps j = k - i in [0, n]
while lo <= hi:
i = (lo + hi) // 2
j = k - i
a_left = a[i - 1] if i > 0 else -math.inf
a_right = a[i] if i < m else math.inf
b_left = b[j - 1] if j > 0 else -math.inf
b_right = b[j] if j < n else math.inf
# FIXED boundary predicate: max(left) <= min(right), both <=
if a_left <= b_right and b_left <= a_right:
return max(a_left, b_left) # the k-th smallest
if a_left > b_right:
hi = i - 1 # take fewer from A
else: # b_left > a_right
lo = i + 1 # take more from A
raise ValueError("unreachable for 1 <= k <= m+n with sorted input")
Why this is now correct: max(left) <= min(right) is equivalent to a_left <= b_right and b_left <= a_right (the other two cross terms are guaranteed by per-array sortedness), a valid cut exists for every k ∈ [1, m+n], and the movement rules are monotone-safe — if a_left > b_right no larger i can be valid; if b_left > a_right no smaller i can be valid. So the search provably terminates on a valid cut and returns max(left), which is exactly the k-th smallest.
**Reproduced failure (before fix).** `A=[0,1,1,2,2,2]`, `B=[0,0,0,0,1,1,2,2]`, `k=2`. The feasible window is `i ∈ [0,2]`. At `i=1, j=1`: `left=[0,0]`, `right=[1,0]` — `max(left)=0 <= min(right)=0`, a genuinely valid cut — but the mixed check `a_left < b_right` reads `0 < 0` → false, wrongly rejected. The search moves `lo` up, hits `a_left=1 > b_right=0` at `i=2`, moves `hi` down, the window dies, and it returns stale `1` instead of the correct `0`. Exactly "left maximum exceeds the right minimum."
**Buggy predicate failure rate.** Random duplicate-heavy stress (`values ∈ {0,1,2}`, sizes 1–8, every k): the mixed predicate is wrong on **140,230 / 1,351,852 checks (10.37%)**; the fixed predicate on **0**.
**Verification suite run** (`python3 kth_problem/verify.py`), all passed:
| Suite | Checks | Result |
|---|---|---|
| Exhaustive: every pair of arrays over `{0,1,2}`, sizes 0–4, every k (covers k=1 and k=m+n) | 103,092 | ✓ |
| Regression `[1,2]` vs `[2,3,3,3]`, all 6 k (incl. k=m+n) | 6 | ✓ |
| Random stress, domain sizes {1,2,3,5,8,50}, 40% of k in first 4, 40% in last 4 | 397,662 | ✓ |
| All-equal arrays `[7]*m` vs `[7]*n`, m,n∈1..8, every k | 960 | ✓ |
| Empty / single-element edges (`[], [5,9]`; `[1,2,3], []`; `[3],[3]`; `[10],[1,10,10]`) | 10 | ✓ |
| Classic examples (`[1,3,5]/[2,4,6]`, `[1,2]/[3,4]`, `[0,0]/[0,0]`) | 7 | ✓ |
| **Total exhaustive + randomized** | **500,754** | ✓ |
**Large-array checks** (m=1M, n=1.5M, duplicate-heavy): k ∈ {1, 2, 3, m/2, m, n, m+n−1, m+n, m+n−1000} plus all-equal `[5]*m` vs `[5]*n` — all matched the merge-walk oracle, each answer in ~0.01 ms (≈21 iterations → O(log(min(m,n)))).{"model": "deepseek-v4-flash", "problem_class": "kth-two-sorted-arrays-invariant", "result": "passed", "tests": 500754}