DAY 13 / 30 · WEEK 2

Binary Search Variations

Goal of the day

Concept

Day 12's template answers one question: "does this exact value exist, and if so, at which index?" Once you find a match, you stop. But two common situations need more:

Duplicates. If the target appears 4 times in a row, which index does standard binary search return? Whichever one mid happens to land on — not necessarily the first or last. To find a specific boundary, you don't stop at the first match: you record it as a candidate and keep narrowing in the same direction, discarding the match itself along with everything past it.

Search on answer. Some problems don't hand you a sorted array at all — they ask "what's the smallest/largest value that satisfies some condition?" (e.g. "the slowest eating speed that still finishes in time"). If that condition is monotonic — false for every value below some threshold, true for every value at or above it — you can binary search the space of possible answers, testing each guess with a feasibility check instead of an array comparison.

Diagram: first vs. last occurrence

Array [2, 4, 4, 4, 4, 7, 9] — target = 4 appears at indices 1-4 2 4 4 4 4 7 9 index 0 first=1 last=4 A plain binary search could land on index 1, 2, 3, or 4 — any of them is a "correct" match. Finding first/last needs two separate, deliberate searches, each biased to keep narrowing after a match instead of stopping. Green = confirmed boundary. Amber = a match found mid-search, still narrowing.

Interactive visualization: find first and last occurrence

current range [lo, hi] mid (checking now) match found, still narrowing eliminated confirmed boundary
Press Play or Step to begin.

The code: first/last occurrence

function searchRange(nums, target) {
  function findBound(isFirst) {
    let lo = 0, hi = nums.length - 1, result = -1;
    while (lo <= hi) {
      const mid = lo + Math.floor((hi - lo) / 2);
      if (nums[mid] === target) {
        result = mid;                     // record this match...
        if (isFirst) hi = mid - 1;        // ...but keep looking further left
        else lo = mid + 1;                // ...or further right
      } else if (nums[mid] < target) lo = mid + 1;
      else hi = mid - 1;
    }
    return result;
  }
  return [findBound(true), findBound(false)];
}

Line by line:

Dry run: findBound(true) — first occurrence of 4 in [2, 4, 4, 4, 4, 7, 9]

lohimidarr[mid]Action
0634match! result=3, keep looking left → hi=2
0214match! result=1, keep looking left → hi=0
00022 < 4 → lo=1
10lo > hi, loop ends → return 1

The search keeps going even after finding a match at index 3 — that's the whole trick. The last confirmed match before the loop ends (index 1) is the true first occurrence.

Diagram: search on answer

Koko's eating speed: is speed v "fast enough to finish in time h"? false — too slow true — fast enough to finish flip point = the answer Binary search the speed axis itself, not an array: guess a speed, check feasibility in O(piles), narrow toward the flip point in O(log(max pile)).

Complexity

VariantTimeSpaceWhy
First/last occurrenceO(log n)O(1)Two independent binary searches over the same n-element array — still logarithmic, just with a factor-of-2 constant.
Search on answer (e.g. Koko)O(n log m)O(1)log m guesses over the answer range (m = largest possible answer), each guess costing O(n) to check feasibility across all n piles.

Interview corner

How to talk through it out loud: name which template you're using and why — "this needs the leftmost/rightmost boundary, so I'm using the narrowing template, not Day 12's exact-match one" — before writing a line of code. Interviewers are listening for whether you're pattern-matching the problem shape to the right template, not just typing something that happens to compile.

Practice problems

1. Sqrt(x) (Easy — LeetCode: Sqrt(x))

Given a non-negative integer x, return the largest integer whose square is ≤ x — without using Math.sqrt.

2. Find First and Last Position of Element in Sorted Array (Medium — LeetCode: Find First and Last Position of Element in Sorted Array)

Given a sorted array with duplicates, return the first and last index of a target value, or [-1, -1] if it's not present. Today's main template, applied directly.

3. Koko Eating Bananas (Medium — LeetCode: Koko Eating Bananas)

Koko has piles of bananas and h hours. Eating at integer speed v bananas/hour, each pile takes ceil(pile / v) hours. Find the minimum speed that finishes all piles within h hours.

Quiz

1. When a target appears multiple times, which index does plain binary search (Day 12's template) return?

2. How does the first-occurrence search change Day 12's template after finding a match?

3. What property must a "search on answer" feasibility check have?

4. What's the time complexity of Koko Eating Bananas' search-on-answer solution?

5. In Sqrt(x)'s solution, why does mid use Math.ceil instead of the usual Math.floor?