DSA Notes · chapter 06 · Java Live Code

Search the answer, not only the index

On a sorted array you throw away a half by comparing to mid. After a rotation you first name which half is still sorted. When the array is not sorted but the predicate “T works” is monotone, you binary-search T itself — Koko, ship packages, aggressive cows.

Prev: 05 Greedy · Next: 07 Sliding Window

06

Binary Search

Three cards: classic inclusive search, rotated array (sorted half), binary search on the answer. Overflow-safe mid in every interview version. Two examples each, the second with every probe.

Classic binary search — sorted array, find index

Recognize it from

“Array is sorted. Return the index of t, or −1.” Variants: first/last occurrence, insert position. Not “binary search the answer” (Koko) — that searches a value domain, not an index.

What it actually does

Each step throws away half of the remaining closed interval [lo, hi]. Compare t to the middle. Equal: done. Smaller: the right half cannot hold t. Larger: the left half cannot. When lo > hi the interval is empty and t was never present.

Simple version is the same idea on the call stack. Interview version is a loop, overflow-safe mid, and lo <= hi so a single remaining cell is tested rather than skipped.

Simple — read this first

public int search(int[] a, int t) {
    return rec(a, t, 0, a.length - 1);
}
private int rec(int[] a, int t, int lo, int hi) {
    if (lo > hi) return -1; // empty slice: t is not here
    int mid = lo + (hi - lo) / 2;
    if (a[mid] == t) return mid;
    if (a[mid] < t) return rec(a, t, mid + 1, hi); // t lives right of mid
    return rec(a, t, lo, mid - 1);
}

Interview — type this

public int search(int[] a, int t) {
    int lo = 0, hi = a.length - 1;
    while (lo <= hi) { // inclusive: a one-cell window is still a search
        int mid = lo + (hi - lo) / 2; // (lo + hi) / 2 overflows when lo, hi are huge
        if (a[mid] == t) return mid;
        if (a[mid] < t) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}
Example 1 — simple

Find 3 in [1, 3, 5].

index 0 1 2 value 1 3 5 lo=0, hi=2, mid=1 → a[1]=3 hit
One probe. Inclusive window [0,2].
lohimid = lo+(hi−lo)/2a[mid]compare to 3next
0213equalreturn 1

Answer: index 1.

Example 2 — every step

Find 6 in [1, 3, 5, 7] — not present. Every lo / hi until the window dies.

index 0 1 2 3 value 1 3 5 7 target 6 sits between 5 and 7
Every iteration until lo > hi. Return −1.
lohimida[mid]6 vs a[mid]update
030+(3−0)/2 = 136 > 3lo = mid+1 = 2
232+(3−2)/2 = 256 > 5lo = mid+1 = 3
333+(3−3)/2 = 376 < 7hi = mid−1 = 2
32loop ends (lo > hi)——return −1

Answer: −1. The last living cell was index 3 (value 7). Shrinking hi to 2 with lo already 3 empties the range. A lo < hi loop with the wrong mid bias can spin here — that is why the interview version keeps <= and always moves lo or hi past mid.

Time O(log n)Space O(1) iterative · O(log n) recursive stack
Why this

Sorted order means one comparison discards a contiguous half. Integer mid must not overflow: on a 2-million-length array lo+hi can exceed Integer.MAX_VALUE. Square-root / insert-position problems reuse this loop on a numeric range instead of an array — see Koko.

Common mistakes
  • mid = (lo + hi) / 2 on large indices.
  • while (lo < hi) without a plan for the last cell — you can exit without testing it.
  • Returning mid when a[mid] is close, not equal.
ProblemPatternLinks
Sqrt / random-pick family (search a number, not an index)same lo/hi on the value domaincheat card
Median of two sorted arrays (cut, not a single mid of one array)binary search a partitioncheat card

Rotated sorted array — which half is sorted

Recognize it from

“Sorted array rotated at an unknown pivot. Find t.” One half of [lo, mid] or [mid, hi] is still monotonic. That is the invariant. Not circular Kadane (max wrap subarray). Not “find the rotation index” unless they asked only for the min.

What it actually does

A rotation splits the array into two sorted runs. At any mid, at least one of the two closed halves is sorted (unless duplicates collapse both — then shrink lo/hi by one). Ask: is the left half sorted? If yes, decide whether t belongs in that numeric range; if not, throw that half away.

Simple version finds the minimum first (the rotation), then runs ordinary binary search on one of the two runs. Interview version does it in one pass so you do not spend a second log n if they only wanted t.

Simple — read this first

public int searchRotatedTwoPass(int[] a, int t) {
    int n = a.length;
    int lo = 0, hi = n - 1;
    while (lo < hi) { // find the rotation: smallest element
        int mid = lo + (hi - lo) / 2;
        if (a[mid] > a[hi]) lo = mid + 1; // min lives to the right of mid
        else hi = mid;
    }
    int k = lo; // index of minimum; [0..k) and [k..n) are both sorted
    int inLeft = bsearch(a, t, 0, k - 1);
    if (inLeft >= 0) return inLeft;
    return bsearch(a, t, k, n - 1);
}
private int bsearch(int[] a, int t, int lo, int hi) {
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == t) return mid;
        if (a[mid] < t) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

Interview — type this

public int search(int[] a, int t) {
    int lo = 0, hi = a.length - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == t) return mid;
        if (a[lo] <= a[mid]) { // left closed interval is sorted
            if (a[lo] <= t && t < a[mid]) hi = mid - 1; // t sits inside that sorted run
            else lo = mid + 1;
        } else { // right closed interval is sorted
            if (a[mid] < t && t <= a[hi]) lo = mid + 1; // t sits inside the sorted right run
            else hi = mid - 1;
        }
    }
    return -1;
}
Example 1 — simple

[4, 5, 6, 1, 2], find 1.

4 5 6 | 1 2 sorted sorted lo=0 hi=4 mid=2 val=6 left [4,5,6] is sorted, 1 is not in [4,6] → go right
One-pass interview walk. Target 1.
lohimida[mid]which half sorted?is t in that half?next
0426left (4 ≤ 6)1 in [4,6)? nolo = 3
3431equalhitreturn 3

Answer: index 3.

Example 2 — every step

[4, 5, 6, 7, 0, 1, 2], find 0. Every mid until the hit.

0 1 2 3 4 5 6 4 5 6 7 0 1 2 ^ target lives in the right run
Every iteration. Target 0. Inclusive lo≤hi.
lohimida[mid]left sorted?0 in the sorted half?update
0637yes (4 ≤ 7)0 in [4,7)? nolo = 4
4651yes (0 ≤ 1)0 in [0,1)? yes (0 ≤ 0 < 1)hi = 4
4440equalhitreturn 4

Answer: index 4. After the first probe you discarded the entire left run [4,5,6,7] with one range check. The second probe’s left half is [0,1] — now the sorted half does contain 0, so you shrink hi onto it.

Time O(log n) distinct values · O(n) with heavy duplicatesSpace O(1)
Why this

One comparison against mid is not enough after a rotation: the sequence is not globally monotonic. Identifying the sorted half restores a range check. Duplicates a[lo]==a[mid]==a[hi] destroy that test — shrink both ends. Circular Kadane wraps a sum, it does not search a pivot.

Common mistakes
  • Treating the whole array as sorted and walking off the rotation.
  • Using t <= a[mid] on a sorted left half including mid after you already returned on equality — off-by-one can drop the only copy.
  • Forgetting n = 1, or an array that was never rotated (still works: left half is always sorted).
ProblemPatternLinks
Search in rotated sorted array (with uni-valued trees in that IE)one half is sorted — range-check tcheat card
Kadane circular (neighbor)wrap-around subarray, not binary searchcheat card

Binary search on the answer — Koko / ship packages

Recognize it from

“Minimum speed / capacity / days so that a linear pack still finishes by H.” Monotone: if speed k works, every k' > k also works. You search k, not an index in piles. Same skeleton: split array / book allocation (min of max load), aggressive cows (max of min gap, inequality flips).

What it actually does

The piles are not sorted in a way that helps. The answer is monotonic. Set lo = 1 (or max element for ship capacity), hi = max pile (or sum of weights). feasible(mid) is one O(n) scan: hours = sum of ceil(pile / mid). If that is ≤ h, try a slower speed (shrink hi). Else raise lo.

Ship-packages is the same shape with a different feasible: load each weight onto the current day until capacity would overflow, then start a new day; succeed if days ≤ D. Aggressive cows / max-min gap flips the predicate: larger D is harder, so a success tries higher. Integer ceil: (p + k − 1) / k, in long.

Simple — read this first

public int minEatingSpeed(int[] piles, int h) {
    int lo = 1, hi = 0;
    for (int p : piles) hi = Math.max(hi, p); // speed max pile finishes in n hours
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (feasible(piles, h, mid)) hi = mid; // mid works: try slower
        else lo = mid + 1;
    }
    return lo;
}
private boolean feasible(int[] piles, int h, int k) {
    int hours = 0;
    for (int p : piles) {
        hours += (int) Math.ceil((double) p / k); // works; float is the thing to drop later
        if (hours > h) return false;
    }
    return hours <= h;
}

Interview — type this

public int minEatingSpeed(int[] piles, int h) {
    int lo = 1, hi = 0;
    for (int p : piles) hi = Math.max(hi, p);
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (feasible(piles, h, mid)) hi = mid;
        else lo = mid + 1;
    }
    return lo;
}
private boolean feasible(int[] piles, int h, int k) {
    long hours = 0; // n * ceil(pile/k) can exceed int
    for (int p : piles) {
        hours += (p + (long) k - 1) / k; // ceil(p/k) for positive ints, no float
        if (hours > h) return false;
    }
    return hours <= h;
}
Example 1 — simple

piles = [3, 6, 7, 11], h = 8. Why 4 works and 3 does not.

hours(k) = ceil(3/k)+ceil(6/k)+ceil(7/k)+ceil(11/k) k=3 → 1+2+3+4 = 10 > 8 too slow k=4 → 1+2+2+3 = 8 ≤ 8 just enough k=6 → 1+1+2+2 = 6 ≤ 8 also works, not minimal
Check the two speeds around the answer. ceil via (p+k−1)/k.
kceil 3/kceil 6/kceil 7/kceil 11/khours≤ 8?
3(3+2)/3=1(6+2)/3=2(7+2)/3=3(11+2)/3=410no
4(3+3)/4=1(6+3)/4=2(7+3)/4=2(11+3)/4=38yes

Minimum feasible speed is 4. Heap-of-piles does not replace this: you are not repeatedly eating from the current largest pile; you pick one constant speed for every hour slot.

Example 2 — every step

Same instance. lo = 1, hi = 11, loop while (lo < hi), hi = mid on success. Every mid until lo == hi.

Binary search on k. feasible = hours(k) ≤ 8. No skipped mid.
lohimidhours(mid)feasible?then
1111+(11−1)/2 = 61+1+2+2 = 6yeshi = 6
161+(6−1)/2 = 31+2+3+4 = 10nolo = 4
464+(6−4)/2 = 51+2+2+3 = 8yeshi = 5
454+(5−4)/2 = 41+2+2+3 = 8yeshi = 4
44stop (lo == hi)——return 4

Answer: 4. Ship-packages on the same numbers would change lo to max(weight) and feasible to “days at capacity mid.” The mid table looks different; the loop skeleton does not.

Time O(n log MAX) Koko · O(n log SUM) shipSpace O(1)
Why this

Monotone boolean on T lets you throw away half the value domain each time. n can be tiny while T’s range is 1e9 — that is why you do not scan T = 1,2,3,… . Maximize-minimum (cows, heaters radius, power allocation) uses the same loop with the success branch moving lo up.

Common mistakes
  • Using a max-heap of piles. That solves a different eat-from-largest problem, not constant speed.
  • Math.ceil(p / k) with integer division — that truncates first, ceil does nothing. Use doubles or (p + k − 1) / k.
  • lo = 0. Division by zero in ceil, and speed 0 never finishes a positive pile.
ProblemPatternLinks
Koko / ship packagesmin feasible T, feasible is a linear packcheat card · clue · clue · clue
Split array / book allocationmin of max load — same feasible shape as shipcheat card
Aggressive cows / heaters / power max-minmaximize minimum; success moves lo upcheat card · cheat card · cheat card · clue

See also

Classic search and rotated search still probe an array index. Koko probes a number you will return. If the prompt is “possible or impossible at budget T” and larger T is easier (or strictly harder), you are on the third card. Server-partition assignment with a min-heap of loads is not this — that is a greedy heap, in chapter 05 of the notebook, not a binary search.