β‘ Key Constraint: needs order + O(1) random access (no LinkList); and the window must strictly shrink β the obvious lo = mid formulation loops forever.
Preconditions β order (so βtoo high/lowβ is meaningful) + O(1) random access to the midpoint.
2. Why Ξ(logn) (the Invariant)
Halving β each pass is O(1) and halves the window βΉ n/2b=1βb=log2βn passes.
Loop invariant β if the key exists in array[0β¦N], it exists in array[loβ¦hi] β the if/else is exactly what preserves it, and it is deliberately the weakest statement implying the postcondition.
Best ξ = worst, and why β the return mid short-circuits βΉ best Ξ(1) (target is the first midpoint), worst Ξ(logn) (target absent). Contrast Linear Searchβs recursive form, which has no early exit on a miss.
3. The Termination Bug (why lo = mid hangs)
The naive form β while lo < hi: mid = (lo+hi)//2; if key >= array[mid]: lo = mid else: hi = mid β no early return, because lo is reused as the answer index.
The stall β at lo=5,hi=6: mid=β11/2β=5, so lo = mid is a no-op βΉ no measure decreases, the guard stays true, the loop spins forever.
The fix β while lo < hi - 1: with hiexclusive, the space may shrink to size 1 and exit, lo holding the answer index.
Why it matters beyond the exam β a non-terminating branch is input-dependent, so it survives testing; the assignment harness kills the thread on timeout and the mark is lost.
4. Boundary Search (the range-reporting entry point)
Search for the boundary, not the value β to report everything in (X,Y), binary-search the smallest element >X β X itself need not be present.
Then scan β walk forward printing until an element β₯Y βΉ Ξ(logn+W) for W reported items β Output-Sensitive Complexity.
βοΈ Core Implementation
πΉ index via binary search (inclusive high, early return)
SortedArrayList.index
def index(self, item: T) -> int: low, high = 0, len(self) - 1 while low <= high: mid = low + (high - low) // 2 # avoids (low+high) overflow in fixed-width ints if self.array[mid] > item: high = mid - 1 # discard right half -- STRICT shrink elif self.array[mid] == item: return mid # found else: low = mid + 1 # discard left half -- STRICT shrink raise ValueError("item not in list") # low > high => absent
π‘ Common Mistake:Use mid = low + (high-low)//2 β (low+high)//2 can overflow in fixed-width ints; and the data must be sorted or discarding a half is unjustified.
πΉ Boundary form (exclusive hi, no early return)
binary_search β the lecture's version, with the terminating guard
def binary_search(array, key): # hi is EXCLUSIVE; we do not exit early because lo IS the answer index lo, hi = 0, len(array) while lo < hi - 1: # NOT `lo < hi` -- that never terminates mid = (lo + hi) // 2 if key >= array[mid]: lo = mid # keep [mid, hi) else: hi = mid # keep [lo, mid) return lo if len(array) > 0 and array[lo] == key else -1
π‘ Common Mistake:lo = mid with guard lo < hi β at hi=lo+1 the midpoint islo, so the assignment is a no-op βΉ infinite loop. mid + 1 or the hi - 1 guard restores the strictly-decreasing variant.
βοΈ Core Decision Matrix
Aspect
Complexity
Why
Time β best
O(Comp)
target is the first midpoint
Time β worst/avg
Ξ(logn)β Comp
window halves each pass
Space (iterative)
O(1)
three indices
Space (recursive)
O(logn)
call stack
When It Flips: vs Linear Search (O(n)) always better; vs Hash Table (O(1) expected but unordered) binary search keeps order for predecessor/successor/range queries and in-order iteration. A balanced Binary Tree is "binary search made dynamic" β O(logn) search and insert/delete, which a sorted array (O(n) inserts) cannot.
Final Extracted Output: halving βΉ log2βn passes of O(1) βΉ Ξ(logn) β vs Linear Searchβs O(n), i.e. ~20 vs 1,000,000 passes on a million elements.
π§ Active Recall
Binary search needs sorted data in an array β why each requirement, and what structure relaxes them while keeping O(logn)?
Hint: Order justifies discarding; array gives the midpoint.
Answer
Short answer:sorted lets you discard a half; array gives O(1) midpoint access.
Why:Dynamic version β a balanced Binary Tree keeps O(logn) search andO(logn) insert/delete, which a sorted array cannot.
Binary search is Ξ(logn); a hash table is O(1) expected β when would you still choose binary search?
Hint: Ordered operations.
Answer
Short answer: when you need predecessor/successor, range queries, or in-order iteration.
"The window shrinks every iteration, so it terminates." Where does that argument fail?
Hint: Shrinks, or strictly shrinks?
Answer
Short answer: with lo = mid and hi=lo+1, the midpoint equals lo, so the window does not shrink and the loop never exits.
Why:A variant must strictly decrease β termination needs hiβlo to drop by at least 1 every iteration; use lo = mid + 1, or guard with while lo < hi - 1 given an exclusivehi β Invariant.