Ch8 Intermediate

Binary Search

Classic Search • Rotated Array • Answer Space • Lower/Upper Bound

11 Sections
2 Solved Problems

Section 1 — What Is Binary Search?

Binary search finds a target in a sorted (or monotone) space by repeatedly halving the search interval. Each comparison eliminates half the remaining candidates, giving O(log n) time — far superior to O(n) linear search for large inputs.

The Core Invariant
  • At every step, the answer (if it exists) lies within [lo, hi].
  • We compute mid = lo + (hi - lo) / 2 (avoids integer overflow vs (lo+hi)/2).
  • We then shrink the window: move lo up or hi down based on the comparison at mid.
  • The loop terminates when lo > hi (classic) or lo == hi (boundary search).
  • Critical: every iteration MUST reduce the window size. If lo or hi never moves, the loop runs forever.

1.1 — The Three Binary Search Templates

Most binary search bugs come from wrong loop condition or wrong boundary update. The following three templates cover 99% of interview problems.

C++
// ── TEMPLATE 1: Classic exact search ────────────────────────
// Use when: searching for an exact value in a sorted array.
// Loop exits when lo > hi (element not found).
int bsClassic(vector<int>& a, int target) {
    int lo = 0, hi = (int)a.size() - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if      (a[mid] == target) return mid;   // found
        else if (a[mid] <  target) lo = mid + 1; // target in right half
        else                       hi = mid - 1; // target in left half
    }
    return -1; // not found
}

// ── TEMPLATE 2: Lower bound ──────────────────────────────────
// Use when: find first index where a[i] >= target.
// Loop exits when lo == hi == insertion point.
int lowerBound(vector<int>& a, int target) {
    int lo = 0, hi = (int)a.size(); // hi = n (one past end) is valid
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] < target) lo = mid + 1; // mid is too small, exclude it
        else                 hi = mid;      // mid could be the answer, keep it
    }
    return lo; // first index with a[lo] >= target
}

// ── TEMPLATE 3: Upper bound ──────────────────────────────────
// Use when: find first index where a[i] > target.
int upperBound(vector<int>& a, int target) {
    int lo = 0, hi = (int)a.size();
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] <= target) lo = mid + 1; // mid is not strictly greater
        else                  hi = mid;
    }
    return lo; // first index with a[lo] > target
}
// C++ equivalents: lower_bound(a.begin(),a.end(),t) and upper_bound(...)

1.2 — Template Comparison

TemplateLoop Conditionhi initialised tolo/hi UpdateReturns
Classic exactlo <= hin - 1lo=mid+1 or hi=mid-1Index or -1
Lower boundlo < hinlo=mid+1 or hi=midFirst idx >= target
Upper boundlo < hinlo=mid+1 or hi=midFirst idx > target
Answer spacelo < hi or <=problem maxdepends on feasibilityOptimal value

Section 2 — Visual Diagrams: Binary Search in Action

Diagram 1 — Classic Binary Search

Trace
Classic Binary Search: Target = 7
  Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]   target = 7
  Index:  0  1  2  3  4   5   6   7   8   9

  Iteration 1: lo=0, hi=9, mid=4, a[mid]=9
    9 > 7  =>  search left half.  hi = mid-1 = 3.
  [1, 3, 5, 7] [9, 11, 13, 15, 17, 19]
   lo=0      hi=3

  Iteration 2: lo=0, hi=3, mid=1, a[mid]=3
    3 < 7  =>  search right half.  lo = mid+1 = 2.
  [1, 3] [5, 7]
          lo=2 hi=3

  Iteration 3: lo=2, hi=3, mid=2, a[mid]=5
    5 < 7  =>  lo = mid+1 = 3.
         [7]
          lo=3=hi=3

  Iteration 4: lo=3, hi=3, mid=3, a[mid]=7
    7 == 7  =>  FOUND at index 3.

  Total comparisons: 4 = ceil(log2(10)).  Without binary search: up to 10.

Diagram 2 — Lower Bound (First Occurrence)

Trace
Lower Bound and Upper Bound Trace
  Array: [1, 3, 3, 3, 5, 7, 9]   target = 3
  Index:  0  1  2  3  4  5  6
  Goal: find FIRST index where a[i] >= 3  (first occurrence of 3)

  lo=0, hi=7 (n=7, one past end)

  Iteration 1: mid=3, a[3]=3
    3 >= target=3  =>  hi = mid = 3.  (keep mid as candidate)
  lo=0, hi=3

  Iteration 2: mid=1, a[1]=3
    3 >= 3  =>  hi = 1.
  lo=0, hi=1

  Iteration 3: mid=0, a[0]=1
    1 < 3  =>  lo = mid+1 = 1.
  lo=1, hi=1  =>  loop exits (lo == hi).

  Return lo = 1.  a[1] = 3 = first occurrence.  Correct!

  Upper bound (first index > 3): returns 4.
  So all occurrences of 3 are in indices [1, 4) = {1, 2, 3}.
  Count of 3s = upperBound - lowerBound = 4 - 1 = 3.  Correct!

Diagram 3 — Binary Search on Answer Space

Trace
Binary Search on Answer Space: Koko Bananas
  Problem: Koko eats bananas. Piles = [3, 6, 7, 11]. H = 8 hours.
  Find minimum eating speed k such that Koko finishes all piles in H hours.

  Key insight: 'Can Koko finish at speed k?' is monotone.
  If YES at speed k, then YES at speed k+1, k+2, ... (faster is always feasible).
  If NO  at speed k, then NO  at speed k-1, k-2, ... (slower is never feasible).
  => Binary search on k!

  Search space: lo=1 (min possible speed), hi=11 (max pile = always works).

  mid=6: hours = ceil(3/6)+ceil(6/6)+ceil(7/6)+ceil(11/6) = 1+1+2+2 = 6 <= 8. YES
  hi = 6.

  mid=3: hours = ceil(3/3)+ceil(6/3)+ceil(7/3)+ceil(11/3) = 1+2+3+4 = 10 > 8. NO
  lo = 4.

  mid=5: hours = ceil(3/5)+ceil(6/5)+ceil(7/5)+ceil(11/5) = 1+2+2+3 = 8 <= 8. YES
  hi = 5.

  mid=4: hours = ceil(3/4)+ceil(6/4)+ceil(7/4)+ceil(11/4) = 1+2+2+3 = 8 <= 8. YES
  hi = 4.

  lo=4 == hi=4  =>  return 4.
  Answer: minimum speed = 4.  Correct!

Diagram 4 — Rotated Sorted Array

Trace
Rotated Array: Which Half Is Sorted?
  Original sorted: [1, 2, 3, 4, 5, 6, 7]
  Rotated at index 3: [4, 5, 6, 7, 1, 2, 3]
  Index:               0  1  2  3  4  5  6

  Key property: at least ONE half is always normally sorted.
  Check: if a[lo] <= a[mid]  =>  left half [lo..mid] is sorted.
         else                =>  right half [mid..hi] is sorted.

  Search for target = 1:
  lo=0, hi=6, mid=3, a[mid]=7.
    a[lo]=4 <= a[mid]=7  =>  LEFT half [0..3] = [4,5,6,7] is sorted.
    Is target 1 in [4, 7]?  1 < 4  =>  NO.  Search right: lo = 4.

  lo=4, hi=6, mid=5, a[mid]=2.
    a[lo]=1 <= a[mid]=2  =>  LEFT half [4..5] = [1,2] is sorted.
    Is target 1 in [1, 2]?  1 >= 1 and 1 <= 2  =>  YES.  Search left: hi = 5.

  lo=4, hi=5, mid=4, a[mid]=1.
    a[mid] == target  =>  FOUND at index 4.

Section 3 — Real-World Use Cases

ApplicationSystemHow Binary Search Is Used
Dictionary / index lookupDatabase B-Tree indexSearch sorted key space in O(log n) I/O pages
Version control bisectgit bisectBinary search through commit history to find first bad commit
IP routingNetwork router lookupLongest prefix match via binary search on sorted prefix table
Load balancingConsistent hashing ringBinary search for the next server on the sorted hash ring
Media streamingVideo player seekBinary search on sorted timestamp index for O(log n) seek
Spell checkerSorted dictionary fileBinary search for word existence / nearest match
CompressionArithmetic / range codingBinary search on cumulative frequency table
SchedulingRate limiter / throttleBinary search on sorted event timestamps for window queries
Machine learningHyperparameter tuningBinary / ternary search on unimodal loss curve
Scientific computingRoot findingBisection method: binary search for f(x)=0 in continuous domain

Section 4 — Core Concepts & Algorithms

4.1 — Search in Rotated Sorted Array

C++
// Rotated Sorted Array Search — O(log n)
// LeetCode 33 — Search in Rotated Sorted Array
// Time: O(log n)  Space: O(1)
// Key: at mid, exactly one half is normally sorted. Use that to guide search.
int search(vector<int>& a, int target) {
    int lo = 0, hi = (int)a.size() - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == target) return mid;

        // Determine which half is sorted
        if (a[lo] <= a[mid]) {           // left half [lo..mid] is sorted
            if (a[lo] <= target && target < a[mid])
                hi = mid - 1;            // target is in sorted left half
            else
                lo = mid + 1;            // target must be in right half
        } else {                         // right half [mid..hi] is sorted
            if (a[mid] < target && target <= a[hi])
                lo = mid + 1;            // target is in sorted right half
            else
                hi = mid - 1;            // target must be in left half
        }
    }
    return -1;
}

4.2 — Find Minimum in Rotated Sorted Array

C++
// Find Minimum in Rotated Array — O(log n)
// LeetCode 153 — Find Minimum in Rotated Sorted Array
// Time: O(log n)  Space: O(1)
// The minimum is always in the UNSORTED (rotated) half.
int findMin(vector<int>& a) {
    int lo = 0, hi = (int)a.size() - 1;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] > a[hi])
            lo = mid + 1;  // min is in right half (right is unsorted)
        else
            hi = mid;      // min is in left half or at mid
    }
    return a[lo]; // lo == hi == index of minimum
}
// Why compare with a[hi] not a[lo]?
// Comparing mid to hi tells us if the right side is 'out of order' (rotated).
// If a[mid] > a[hi]: right half wraps around, minimum is in [mid+1, hi].
// If a[mid] <= a[hi]: right half is sorted normally, minimum is in [lo, mid].

4.3 — Binary Search on Answer Space

Many optimisation problems can be solved by binary searching on the answer value directly. The key insight: define a feasibility function f(x) that returns true/false. If f is monotone (all true for x >= answer, all false below), binary search finds the boundary.

C++
// Binary Search on Answer Space — O(n log(max_val))
// Generic answer-space binary search template
// Find minimum x such that feasible(x) is true.
int answerSpaceBS(int lo, int hi) {
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (feasible(mid))
            hi = mid;      // mid works, try smaller
        else
            lo = mid + 1;  // mid does not work, must go larger
    }
    return lo; // minimum feasible value
}

// Koko Eating Bananas (LC 875)
// feasible(k): can Koko eat all piles at speed k in H hours?
bool feasible(vector<int>& piles, int k, int H) {
    long long hours = 0;
    for (int p : piles) hours += (p + k - 1) / k; // ceil(p/k)
    return hours <= H;
}
int minEatingSpeed(vector<int>& piles, int h) {
    int lo = 1, hi = *max_element(piles.begin(), piles.end());
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (feasible(piles, mid, h)) hi = mid;
        else                         lo = mid + 1;
    }
    return lo;
}

4.4 — Counting Occurrences with Lower/Upper Bound

C++
// Lower/Upper Bound Applications
// Count occurrences of target in sorted array
// Time: O(log n)  Space: O(1)
int countOccurrences(vector<int>& a, int target) {
    // lower_bound: first index with a[i] >= target
    int lo = (int)(lower_bound(a.begin(), a.end(), target) - a.begin());
    // upper_bound: first index with a[i] > target
    int hi = (int)(upper_bound(a.begin(), a.end(), target) - a.begin());
    return hi - lo; // number of elements equal to target
}

// First and last position of target (LC 34)
vector<int> searchRange(vector<int>& a, int target) {
    int first = (int)(lower_bound(a.begin(),a.end(),target) - a.begin());
    if (first == (int)a.size() || a[first] != target) return {-1,-1};
    int last  = (int)(upper_bound(a.begin(),a.end(),target) - a.begin()) - 1;
    return {first, last};
}

4.5 — Binary Search on 2D Matrix

C++
// Binary Search on 2D Matrix — O(log(mn)) and O(m+n)
// LeetCode 74 — Search a 2D Matrix
// Matrix: rows sorted, last element of row < first element of next row.
// => Treat as a flattened 1D sorted array of m*n elements.
// Time: O(log(m*n))  Space: O(1)
bool searchMatrix(vector<vector<int>>& mat, int target) {
    int m = mat.size(), n = mat[0].size();
    int lo = 0, hi = m * n - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        int val = mat[mid / n][mid % n];  // convert 1D index to 2D
        if      (val == target) return true;
        else if (val <  target) lo = mid + 1;
        else                   hi = mid - 1;
    }
    return false;
}

// LeetCode 240 — Search a 2D Matrix II
// Each row sorted, each column sorted (but rows don't connect).
// Use staircase search from top-right corner: O(m+n)
bool searchMatrixII(vector<vector<int>>& mat, int target) {
    int r = 0, c = (int)mat[0].size() - 1;  // start top-right
    while (r < (int)mat.size() && c >= 0) {
        if      (mat[r][c] == target) return true;
        else if (mat[r][c] >  target) c--;  // too big: move left
        else                          r++;  // too small: move down
    }
    return false;
}

Section 5 — Pattern Recognition Guide

If the problem asks...Binary Search VariantKey Setup
Find exact value in sorted arrayClassic (Template 1)lo=0, hi=n-1, lo<=hi
First position of target (or >= target)Lower bound (Template 2)lo=0, hi=n, lo<hi
Last position of targetUpper bound - 1upperBound(target) - 1
Count occurrences of targetupperBound - lowerBoundBoth in O(log n)
Search in rotated sorted arrayRotated BSIdentify sorted half at each step
Find minimum in rotated arrayCompare mid to hiMinimum is in unsorted half
Minimum feasible value (Koko, capacity)Answer space BSBinary search on answer, check feasibility
Maximum feasible valueAnswer space BS (reversed)Flip condition: search for last true
Peak element in arrayBinary search on slopeMove toward the higher neighbour
Square root / power searchAnswer space BSlo=1, hi=target, check mid*mid
Search in 2D matrix (rows+cols connected)Flatten to 1Dindex (r,c) = (mid/n, mid%n)
Search in 2D matrix (rows+cols sorted)Staircase from top-rightO(m+n), not binary search
🔍 How to Identify an Answer-Space Binary Search Problem
  • SIGNAL 1: The problem asks for a minimum or maximum VALUE satisfying some condition.
  • SIGNAL 2: You can define a yes/no feasibility function f(x) that is monotone (all NO below threshold, all YES above).
  • SIGNAL 3: Keywords: 'minimise the maximum', 'maximum minimum', 'smallest k such that', 'allocate optimally'.
  • SETUP: lo = smallest possible answer, hi = largest possible answer (often max element or sum).
  • DIRECTION: minimise answer -> on feasible, go left (hi=mid). Maximise answer -> on feasible, go right (lo=mid).
  • EXAMPLES: Koko Bananas, Capacity to Ship Packages, Split Array Largest Sum, Magnetic Force Between Balls.
🛡️ The Off-By-One Survival Guide
  • Use lo + (hi - lo) / 2 always — never (lo + hi) / 2. Avoids integer overflow.
  • Classic search (exact): lo <= hi, update lo=mid+1 or hi=mid-1.
  • Boundary search (lower/upper bound): lo < hi, update lo=mid+1 or hi=mid (NOT mid-1!).
  • hi = n (not n-1) for lower/upper bound — allows returning n (insert at end).
  • Never set hi = mid - 1 in a lower-bound template — you will skip the answer.
  • After the loop: lo == hi == the answer index. No need to check both.

Section 6 — Complete C++ Implementations

6.1 — Capacity to Ship Packages (Answer Space BS)

C++
// Capacity to Ship — O(n log sum)
// LeetCode 1011 — Capacity to Ship Packages Within D Days
// Find minimum ship capacity to deliver all packages within D days.
// Time: O(n log(sum))  Space: O(1)
class Solution {
    bool canShip(vector<int>& weights, int cap, int days) {
        int d = 1, load = 0;
        for (int w : weights) {
            if (load + w > cap) { d++; load = 0; } // start new day
            load += w;
        }
        return d <= days;
    }
public:
    int shipWithinDays(vector<int>& weights, int days) {
        // lo: must hold heaviest package; hi: ship all at once
        int lo = *max_element(weights.begin(), weights.end());
        int hi = accumulate(weights.begin(), weights.end(), 0);
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if (canShip(weights, mid, days)) hi = mid;
            else                             lo = mid + 1;
        }
        return lo;
    }
};

6.2 — Find Peak Element

C++
// Find Peak Element — O(log n)
// LeetCode 162 — Find Peak Element
// A peak is an element strictly greater than its neighbours.
// Time: O(log n)  Space: O(1)
// Key insight: always move toward the higher neighbour.
// A peak must exist in that direction (by the rising slope argument).
int findPeakElement(vector<int>& a) {
    int lo = 0, hi = (int)a.size() - 1;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] < a[mid+1])
            lo = mid + 1;  // slope going up: peak is to the right
        else
            hi = mid;      // slope going down: peak is at mid or left
    }
    return lo; // lo == hi == index of a peak
}

6.3 — Split Array Largest Sum

C++
// Split Array Largest Sum — O(n log sum)
// LeetCode 410 — Split Array Largest Sum
// Split nums into k non-empty subarrays to minimise the largest subarray sum.
// Time: O(n log(sum))  Space: O(1)
class Solution {
    bool canSplit(vector<int>& nums, int k, int limit) {
        int parts = 1, curr = 0;
        for (int x : nums) {
            if (curr + x > limit) { parts++; curr = 0; }
            curr += x;
        }
        return parts <= k;
    }
public:
    int splitArray(vector<int>& nums, int k) {
        int lo = *max_element(nums.begin(), nums.end());
        int hi = accumulate(nums.begin(), nums.end(), 0);
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if (canSplit(nums, k, mid)) hi = mid;
            else                        lo = mid + 1;
        }
        return lo;
    }
};

Section 7 — Complexity Reference

AlgorithmTimeSpace
Classic binary search (exact)O(log n)O(1)
Lower bound / upper boundO(log n)O(1)
First and last position of targetO(log n)O(1)
Search in rotated sorted arrayO(log n)O(1)
Find minimum in rotated arrayO(log n)O(1)
Find peak elementO(log n)O(1)
Koko bananas / eating speedO(n log M) M = max pileO(1)
Capacity to ship (D days)O(n log S) S = sumO(1)
Split array largest sumO(n log S) S = sumO(1)
Search in 2D matrix (LC 74)O(log(m*n))O(1)
Search in 2D matrix II (LC 240)O(m + n)O(1)
Count occurrences (lower+upper)O(log n)O(1)
Square root integerO(log n)O(1)
Why is Binary Search O(log n)?
  • Each iteration halves the search window: n -> n/2 -> n/4 -> ... -> 1.
  • After k iterations, window size = n / 2^k. Loop ends when n / 2^k = 1, so k = log2(n).
  • For n = 10^9: log2(10^9) ~ 30 iterations. Linear search would need up to 10^9.
  • Answer-space binary search: O(log(hi-lo)) iterations * O(feasibility check) per iteration.
  • If feasibility check is O(n), total is O(n log(hi-lo)). For Koko: O(n log(max_pile)).

Section 8 — Solved Problem 1: Search in Rotated Sorted Array

1. Observations & Core Idea

Given an integer array nums sorted in ascending order that has been rotated at an unknown pivot, and a target value, return the index of target or -1 if not present. Must run in O(log n).

  • A rotated sorted array like [4,5,6,7,0,1,2] is NOT globally sorted, so naive binary search fails.
  • Key insight: Even after rotation, at least one of the two halves [lo..mid] or [mid..hi] is ALWAYS sorted normally. We can determine which by comparing a[lo] with a[mid].
  • If a[lo] <= a[mid]: the left half [lo..mid] is sorted. Check if target falls within [a[lo], a[mid]). If yes, search left; otherwise search right.
  • If a[lo] > a[mid]: the right half [mid..hi] is sorted. Check if target falls within (a[mid], a[hi]]. If yes, search right; otherwise search left.

2. Approach Comparison

ApproachTimeSpaceMethod
Linear ScanO(n)O(1)Check every element until found.
Rotated Binary SearchO(log n)O(1)Identify sorted half, eliminate half array per step.

3. Optimised Solution

C++
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int lo = 0, hi = (int)nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) return mid;

            if (nums[lo] <= nums[mid]) {
                // Left half [lo..mid] is sorted
                if (nums[lo] <= target && target < nums[mid])
                    hi = mid - 1;   // target in sorted left half
                else
                    lo = mid + 1;   // target in right half
            } else {
                // Right half [mid..hi] is sorted
                if (nums[mid] < target && target <= nums[hi])
                    lo = mid + 1;   // target in sorted right half
                else
                    hi = mid - 1;   // target in left half
            }
        }
        return -1;
    }
};

4. Follow-Up Questions

  • Q: What if the array has duplicates (LC 81)? When a[lo] == a[mid], we cannot determine which half is sorted. Increment lo (lo++) to skip the duplicate and continue. Worst case degrades to O(n).
  • Q: Find the pivot index? Binary search for the minimum element (LC 153). The pivot is the index of the minimum.
  • Q: Why use a[lo] <= a[mid] instead of <? When lo == mid (two-element window), a[lo] == a[mid] and the left half of size 1 is trivially sorted. The <= safely handles this.

Section 9 — Solved Problem 2: Koko Eating Bananas

1. Observations & Core Idea

Koko has piles of bananas. She eats at speed k (k bananas per hour). Each hour she picks one pile and eats min(pile, k) bananas. Find the minimum k so she can eat all bananas in at most h hours.

  • For a given speed k, hours needed = sum of ceil(pile[i] / k) over all piles.
  • Monotone property: If speed k is feasible, then any speed k' > k is also feasible. This enables binary search on k.
  • Search space: lo = 1, hi = max(piles).
  • Binary search finds the minimum k where feasible(k) is true. This is the lower-bound template on the answer space.

2. Approach Comparison

ApproachTimeSpaceMethod
Brute Force (Linear Scan)O(n * M)O(1)Try every speed from 1 to M (max pile).
Binary Search on AnswerO(n log M)O(1)Binary search lo to hi boundary.

3. Optimised Solution

C++
class Solution {
    // Can Koko finish all piles at speed k within h hours?
    bool feasible(vector<int>& piles, long long k, int h) {
        long long hours = 0;
        for (int p : piles)
            hours += (p + k - 1) / k; // ceil(p / k)
        return hours <= h;
    }
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int lo = 1;
        int hi = *max_element(piles.begin(), piles.end());
        // Binary search: find minimum k where feasible(k) is true
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if (feasible(piles, mid, h))
                hi = mid;      // mid works, try smaller
            else
                lo = mid + 1;  // mid too slow, need faster
        }
        return lo; // lo == hi == minimum feasible speed
    }
};

4. Follow-Up Questions

  • Q: Capacity to Ship Packages (LC 1011)? Identical structure. feasible(cap): simulate loading, increment day. lo = max weight, hi = sum of weights.
  • Q: Split Array Largest Sum (LC 410)? Same template. lo = max element, hi = total sum.
  • Q: Why ceil(p/k) = (p + k - 1) / k? In integer math for positive integers a and b, ceil(a/b) = (a + b - 1) / b. Verify: ceil(7/3) = (7+2)/3 = 9/3 = 3.

Section 10 — Common Mistakes & Edge Cases

10.1 — Classic Off-By-One Errors

  • MISTAKE: Using (lo + hi) / 2. When lo and hi are both large, lo + hi overflows a 32-bit integer. Always use lo + (hi - lo) / 2.
  • MISTAKE: Using hi = mid - 1 in a lower-bound template. If mid is the answer, this skips it. Lower-bound must use hi = mid.
  • MISTAKE: Using lo <= hi in a lower-bound search. This can loop forever when lo == hi. Lower-bound uses lo < hi.
  • MISTAKE: Initialising hi = n - 1 for lower/upper bound. The answer can be n (insert at end). Always initialise hi = n.

10.2 — Rotated Array & Answer Space Mistakes

  • MISTAKE: strict less in rotated array search: using a[lo] < a[mid] instead of <=. Fails on a two-element window.
  • MISTAKE: integer overflow in feasibility check. Use long long for accumulated sums or counts (like hours needed for Koko).
  • MISTAKE: confusing minimise vs maximise answer space. Draw the YES/NO monotone map. If you want the first YES (minimise), use hi = mid on YES. If you want the last YES (maximise), use lo = mid on YES.
Warning

Edge Cases to Consider:

  • Single-element array: lo==hi==mid. Classic search works.
  • All elements equal (e.g. [3,3,3]): lower_bound returns 0, upper_bound returns n.
  • Target larger than all elements: lower_bound and upper_bound both return n.

Section 11 — Common Interview Questions

Recommended progression for Binary Search:

#ProblemDifficultyKey Concept
1704. Binary SearchEasyTemplate 1, exact search
235. Search Insert PositionEasyLower bound, return lo
334. Find First and Last PositionMediumLower + upper bound
433. Search in Rotated Sorted ArrayMediumIdentify sorted half
5153. Find Minimum in Rotated Sorted ArrayMediumCompare mid to hi
674. Search a 2D MatrixMediumFlatten to 1D binary search
7875. Koko Eating BananasMediumAnswer space: minimise
81011. Capacity to Ship PackagesMediumAnswer space: min capacity