一键导入
algo-binary-search
Binary search invariants, half-open intervals, and the `lo<hi` template that beats off-by-one bugs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Binary search invariants, half-open intervals, and the `lo<hi` template that beats off-by-one bugs.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | algo-binary-search |
| description | Binary search invariants, half-open intervals, and the `lo<hi` template that beats off-by-one bugs. |
| when-to-use | Sorted array lookups, first/last index of a value, smallest x s.t. f(x) is true, capacity/feasibility search. |
The off-by-one bug rate on hand-written binary search is famously
high (Bentley reported >90% of in-the-wild implementations had a
bug). Pick one template and stick to it. The half-open [lo, hi)
form below works for every variant — search, lower_bound,
upper_bound, predicate-search — by changing only what mid does.
lo is the smallest index that might be the answer.hi is one past the largest index that might be the answer.[lo, hi). When lo == hi, the search is over.mid = lo + (hi - lo) // 2 (avoids overflow in languages that
care; in Python it does not, but use it anyway for muscle memory).O(log n) worst case for sorted-array search.O(1) extra.[lo, hi) is monotone, the same shape
generalises to capacity / feasibility searches over an integer
range — no array needed.def lower_bound(arr: list[int], target: int) -> int:
"""Smallest i s.t. arr[i] >= target. Returns len(arr) if none."""
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < target:
lo = mid + 1
else:
hi = mid
return lo
For upper_bound (smallest i s.t. arr[i] > target) flip the
comparator to <=. For "exact match" check i < len and arr[i] == target after the loop. Stick to [lo, hi) and you never write
hi - 1 again.
// Smallest x in [lo, hi) s.t. ok(x) is true. Returns hi if none.
function searchPredicate(lo, hi, ok) {
while (lo < hi) {
const mid = lo + Math.floor((hi - lo) / 2);
if (ok(mid)) hi = mid;
else lo = mid + 1;
}
return lo;
}
This shape solves "smallest capacity that ships all packages in D
days," "minimum k for which task X passes," and similar boundary
problems. The predicate must be monotone: once ok(x) is true, it
stays true for all x' >= x.
[lo, hi] ranges with <= in the loop. Easy to write
an infinite loop (lo == hi == mid and neither side advances).
The half-open form makes termination obvious — the interval
shrinks every iteration.mid = (lo + hi) // 2 in C/Java: integer overflow on big
arrays. Use lo + (hi - lo) // 2.O(log n) over
random data returns garbage.lower_bound is the
right tool for the latter.ok
becomes false past some threshold, search the negation: replace
ok(x) with not ok(x) and find the boundary.len(arr) == 0 cleanly.lower_bound returns
the first).lo (always-true) and at
hi (always-false).When in doubt, write the predicate, draw the [F, F, ..., T, T, ...]
boundary, and the half-open template gives you the index of the
first T.
Breadth-first search — shortest path on unweighted graphs, level-order traversal, and the `visited` discipline that prevents O(2^n) blowups.
Depth-first search — recursive vs iterative, recursion-depth gotchas, three-color cycle detection, topological sort.
Dynamic programming — state design, memoization vs tabulation, dimension-reduction, and when DP is the wrong tool.
Pick the right traversal — BFS for unweighted shortest path, Dijkstra for weighted, A* for goal-directed, 0/1-BFS for binary weights.
Greedy algorithms — exchange-argument proofs, when greedy beats DP, classic patterns (interval scheduling, Huffman, scheduling).
Hash maps and sets — when O(1) lookup pays off, hash collisions, frozen keys, and hashing custom types.