Binary Search — Practice Tasks¶
15 graded practice problems with full Go / Java / Python solutions. Each task starts with a problem statement, gives constraints, then provides three implementations. Difficulty grows from "warmup" to "this is hard".
Task 1 — Classic Binary Search¶
Problem. Given a sorted array nums[] and a target t, return the index of t, or -1 if absent.
Constraints. 1 <= n <= 10^5, distinct values.
Go:
func bsearch(nums []int, t int) int {
lo, hi := 0, len(nums)-1
for lo <= hi {
m := lo + (hi-lo)/2
if nums[m] == t { return m }
if nums[m] < t { lo = m + 1 } else { hi = m - 1 }
}
return -1
}
Java:
int bsearch(int[] nums, int t) {
int lo = 0, hi = nums.length - 1;
while (lo <= hi) {
int m = lo + (hi - lo) / 2;
if (nums[m] == t) return m;
if (nums[m] < t) lo = m + 1; else hi = m - 1;
}
return -1;
}
Python:
def bsearch(nums, t):
lo, hi = 0, len(nums) - 1
while lo <= hi:
m = (lo + hi) // 2
if nums[m] == t: return m
if nums[m] < t: lo = m + 1
else: hi = m - 1
return -1
Task 2 — Recursive Variant¶
Problem. Same as Task 1, but written recursively.
Go:
func bsearchRec(nums []int, t int) int { return rec(nums, t, 0, len(nums)-1) }
func rec(a []int, t, lo, hi int) int {
if lo > hi { return -1 }
m := lo + (hi-lo)/2
if a[m] == t { return m }
if a[m] < t { return rec(a, t, m+1, hi) }
return rec(a, t, lo, m-1)
}
Java:
int bsearchRec(int[] a, int t) { return rec(a, t, 0, a.length - 1); }
int rec(int[] a, int t, int lo, int hi) {
if (lo > hi) return -1;
int m = lo + (hi - lo) / 2;
if (a[m] == t) return m;
if (a[m] < t) return rec(a, t, m + 1, hi);
return rec(a, t, lo, m - 1);
}
Python:
def bsearch_rec(a, t):
def rec(lo, hi):
if lo > hi: return -1
m = (lo + hi) // 2
if a[m] == t: return m
if a[m] < t: return rec(m + 1, hi)
return rec(lo, m - 1)
return rec(0, len(a) - 1)
Task 3 — Find First Occurrence¶
Problem. Sorted array with duplicates. Return the smallest index i where a[i] == t, or -1.
Go:
func findFirst(a []int, t int) int {
lo, hi, ans := 0, len(a)-1, -1
for lo <= hi {
m := lo + (hi-lo)/2
if a[m] == t { ans = m; hi = m - 1 } else
if a[m] < t { lo = m + 1 } else { hi = m - 1 }
}
return ans
}
Java:
int findFirst(int[] a, int t) {
int lo = 0, hi = a.length - 1, ans = -1;
while (lo <= hi) {
int m = lo + (hi - lo) / 2;
if (a[m] == t) { ans = m; hi = m - 1; }
else if (a[m] < t) lo = m + 1;
else hi = m - 1;
}
return ans;
}
Python:
def find_first(a, t):
lo, hi, ans = 0, len(a) - 1, -1
while lo <= hi:
m = (lo + hi) // 2
if a[m] == t: ans = m; hi = m - 1
elif a[m] < t: lo = m + 1
else: hi = m - 1
return ans
Task 4 — Find Last Occurrence¶
Problem. Symmetric to Task 3 — return the largest index i where a[i] == t.
Go:
func findLast(a []int, t int) int {
lo, hi, ans := 0, len(a)-1, -1
for lo <= hi {
m := lo + (hi-lo)/2
if a[m] == t { ans = m; lo = m + 1 } else
if a[m] < t { lo = m + 1 } else { hi = m - 1 }
}
return ans
}
Java:
int findLast(int[] a, int t) {
int lo = 0, hi = a.length - 1, ans = -1;
while (lo <= hi) {
int m = lo + (hi - lo) / 2;
if (a[m] == t) { ans = m; lo = m + 1; }
else if (a[m] < t) lo = m + 1;
else hi = m - 1;
}
return ans;
}
Python:
def find_last(a, t):
lo, hi, ans = 0, len(a) - 1, -1
while lo <= hi:
m = (lo + hi) // 2
if a[m] == t: ans = m; lo = m + 1
elif a[m] < t: lo = m + 1
else: hi = m - 1
return ans
Task 5 — lower_bound and upper_bound¶
Problem. Implement both lower_bound(a, t) (smallest i with a[i] >= t) and upper_bound(a, t) (smallest i with a[i] > t).
Go:
func lowerBound(a []int, t int) int {
lo, hi := 0, len(a)
for lo < hi {
m := lo + (hi-lo)/2
if a[m] < t { lo = m + 1 } else { hi = m }
}
return lo
}
func upperBound(a []int, t int) int {
lo, hi := 0, len(a)
for lo < hi {
m := lo + (hi-lo)/2
if a[m] <= t { lo = m + 1 } else { hi = m }
}
return lo
}
Java:
int lowerBound(int[] a, int t) {
int lo = 0, hi = a.length;
while (lo < hi) { int m = lo + (hi - lo) / 2; if (a[m] < t) lo = m + 1; else hi = m; }
return lo;
}
int upperBound(int[] a, int t) {
int lo = 0, hi = a.length;
while (lo < hi) { int m = lo + (hi - lo) / 2; if (a[m] <= t) lo = m + 1; else hi = m; }
return lo;
}
Python:
from bisect import bisect_left, bisect_right
def lower_bound(a, t): return bisect_left(a, t)
def upper_bound(a, t): return bisect_right(a, t)
Task 6 — Search in Rotated Sorted Array¶
Problem. A sorted array was rotated at an unknown pivot (e.g., [4,5,6,7,0,1,2]). Find target in O(log n).
Go:
func searchRotated(a []int, t int) int {
lo, hi := 0, len(a)-1
for lo <= hi {
m := lo + (hi-lo)/2
if a[m] == t { return m }
if a[lo] <= a[m] {
if a[lo] <= t && t < a[m] { hi = m - 1 } else { lo = m + 1 }
} else {
if a[m] < t && t <= a[hi] { lo = m + 1 } else { hi = m - 1 }
}
}
return -1
}
Java: identical structure to Go (see interview.md Challenge 4).
Python:
def search_rotated(a, t):
lo, hi = 0, len(a) - 1
while lo <= hi:
m = (lo + hi) // 2
if a[m] == t: return m
if a[lo] <= a[m]:
if a[lo] <= t < a[m]: hi = m - 1
else: lo = m + 1
else:
if a[m] < t <= a[hi]: lo = m + 1
else: hi = m - 1
return -1
Task 7 — Find Peak Element¶
Problem. Find any index i where a[i] > a[i-1] and a[i] > a[i+1] (treat out-of-bounds as -∞). Array may be unsorted.
Go:
func findPeak(a []int) int {
lo, hi := 0, len(a)-1
for lo < hi {
m := lo + (hi-lo)/2
if a[m] > a[m+1] { hi = m } else { lo = m + 1 }
}
return lo
}
Java/Python: mirror.
int findPeak(int[] a) {
int lo = 0, hi = a.length - 1;
while (lo < hi) { int m = lo + (hi - lo) / 2; if (a[m] > a[m+1]) hi = m; else lo = m + 1; }
return lo;
}
def find_peak(a):
lo, hi = 0, len(a) - 1
while lo < hi:
m = (lo + hi) // 2
if a[m] > a[m+1]: hi = m
else: lo = m + 1
return lo
Task 8 — Integer Square Root via Binary Search¶
Problem. Given x >= 0, return ⌊√x⌋ (without using math.sqrt).
Go:
func isqrt(x int) int {
if x < 2 { return x }
lo, hi := 1, x/2 + 1
for lo < hi {
m := lo + (hi-lo)/2
if m*m > x { hi = m } else { lo = m + 1 }
}
return lo - 1
}
Java:
int isqrt(int x) {
if (x < 2) return x;
long lo = 1, hi = x / 2 + 1;
while (lo < hi) {
long m = lo + (hi - lo) / 2;
if (m * m > x) hi = m; else lo = m + 1;
}
return (int)(lo - 1);
}
Python:
def isqrt(x):
if x < 2: return x
lo, hi = 1, x // 2 + 1
while lo < hi:
m = (lo + hi) // 2
if m * m > x: hi = m
else: lo = m + 1
return lo - 1
Task 9 — Binary Search on Float Range¶
Problem. Find r such that r * r * r = x (cube root) with precision 1e-9.
Python:
def cbrt(x, eps=1e-9):
sign = 1 if x >= 0 else -1
x = abs(x)
lo, hi = 0.0, max(1.0, x)
for _ in range(100):
m = (lo + hi) / 2
if m * m * m > x:
hi = m
else:
lo = m
return sign * (lo + hi) / 2
Go:
func cbrt(x float64) float64 {
sign := 1.0
if x < 0 { sign = -1.0; x = -x }
lo, hi := 0.0, math.Max(1.0, x)
for i := 0; i < 100; i++ {
m := (lo + hi) / 2
if m*m*m > x { hi = m } else { lo = m }
}
return sign * (lo + hi) / 2
}
Java:
double cbrt(double x) {
double sign = x < 0 ? -1 : 1; x = Math.abs(x);
double lo = 0, hi = Math.max(1, x);
for (int i = 0; i < 100; i++) {
double m = (lo + hi) / 2;
if (m * m * m > x) hi = m; else lo = m;
}
return sign * (lo + hi) / 2;
}
Task 10 — Capacity to Ship Packages Within D Days (LeetCode 1011)¶
Problem. Given weights[] (must be shipped in given order) and D days, find the minimum truck capacity that allows shipping in D days.
Python:
def shipWithinDays(weights, D):
def days_needed(cap):
days, load = 1, 0
for w in weights:
if load + w > cap:
days += 1; load = 0
load += w
return days
lo, hi = max(weights), sum(weights)
while lo < hi:
m = (lo + hi) // 2
if days_needed(m) <= D: hi = m
else: lo = m + 1
return lo
Go:
func shipWithinDays(weights []int, D int) int {
daysNeeded := func(cap int) int {
days, load := 1, 0
for _, w := range weights {
if load+w > cap { days++; load = 0 }
load += w
}
return days
}
lo, hi := 0, 0
for _, w := range weights {
if w > lo { lo = w }
hi += w
}
for lo < hi {
m := lo + (hi-lo)/2
if daysNeeded(m) <= D { hi = m } else { lo = m + 1 }
}
return lo
}
Java:
public int shipWithinDays(int[] weights, int D) {
int lo = 0, hi = 0;
for (int w : weights) { lo = Math.max(lo, w); hi += w; }
while (lo < hi) {
int m = lo + (hi - lo) / 2;
if (daysNeeded(weights, m) <= D) hi = m;
else lo = m + 1;
}
return lo;
}
private int daysNeeded(int[] w, int cap) {
int days = 1, load = 0;
for (int x : w) {
if (load + x > cap) { days++; load = 0; }
load += x;
}
return days;
}
Task 11 — Split Array Largest Sum (LeetCode 410)¶
Problem. Split nums[] into m non-empty contiguous subarrays minimizing the largest subarray sum. Return that minimized maximum.
Python:
def splitArray(nums, m):
def can_split(max_sum):
parts, cur = 1, 0
for x in nums:
if cur + x > max_sum:
parts += 1; cur = 0
cur += x
return parts <= m
lo, hi = max(nums), sum(nums)
while lo < hi:
mid = (lo + hi) // 2
if can_split(mid): hi = mid
else: lo = mid + 1
return lo
Go/Java: structurally identical to Task 10 with parts <= m predicate.
Task 12 — Search in 2D Sorted Matrix (LeetCode 74)¶
Problem. Matrix where each row is sorted left-to-right and each row's first value > previous row's last value. Find target.
Strategy: treat the matrix as a flat array of length m*n, binary-search by index, decompose to (row, col) = (idx / n, idx % n).
Python:
def searchMatrix(mat, t):
m, n = len(mat), len(mat[0])
lo, hi = 0, m * n - 1
while lo <= hi:
idx = (lo + hi) // 2
v = mat[idx // n][idx % n]
if v == t: return True
if v < t: lo = idx + 1
else: hi = idx - 1
return False
Go:
func searchMatrix(mat [][]int, t int) bool {
m, n := len(mat), len(mat[0])
lo, hi := 0, m*n-1
for lo <= hi {
idx := (lo + hi) / 2
v := mat[idx/n][idx%n]
if v == t { return true }
if v < t { lo = idx + 1 } else { hi = idx - 1 }
}
return false
}
Java:
public boolean searchMatrix(int[][] mat, int t) {
int m = mat.length, n = mat[0].length;
int lo = 0, hi = m * n - 1;
while (lo <= hi) {
int idx = lo + (hi - lo) / 2;
int v = mat[idx / n][idx % n];
if (v == t) return true;
if (v < t) lo = idx + 1; else hi = idx - 1;
}
return false;
}
Task 13 — Exponential Search¶
Problem. Sorted array of unknown length (or known but the target is near the front). Implement exponential + binary.
Python:
def exponential_search(a, t):
if not a: return -1
if a[0] == t: return 0
n, b = len(a), 1
while b < n and a[b] < t:
b *= 2
lo, hi = b // 2, min(b, n - 1)
while lo <= hi:
m = (lo + hi) // 2
if a[m] == t: return m
if a[m] < t: lo = m + 1
else: hi = m - 1
return -1
Go:
func expSearch(a []int, t int) int {
if len(a) == 0 { return -1 }
if a[0] == t { return 0 }
n, b := len(a), 1
for b < n && a[b] < t { b *= 2 }
lo, hi := b/2, b
if hi >= n { hi = n - 1 }
for lo <= hi {
m := (lo + hi) / 2
if a[m] == t { return m }
if a[m] < t { lo = m + 1 } else { hi = m - 1 }
}
return -1
}
Java:
int expSearch(int[] a, int t) {
if (a.length == 0) return -1;
if (a[0] == t) return 0;
int n = a.length, b = 1;
while (b < n && a[b] < t) b *= 2;
int lo = b / 2, hi = Math.min(b, n - 1);
while (lo <= hi) {
int m = lo + (hi - lo) / 2;
if (a[m] == t) return m;
if (a[m] < t) lo = m + 1; else hi = m - 1;
}
return -1;
}
Task 14 — Ternary Search for Unimodal Function Maximum¶
Problem. Given a unimodal function f on [lo, hi] (integer domain), find the index of the maximum.
Python:
def ternary_max(lo, hi, f):
while hi - lo > 2:
m1 = lo + (hi - lo) // 3
m2 = hi - (hi - lo) // 3
if f(m1) < f(m2): lo = m1 + 1
else: hi = m2 - 1
return max(range(lo, hi + 1), key=f)
Go:
func ternaryMax(lo, hi int, f func(int) int) int {
for hi-lo > 2 {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if f(m1) < f(m2) { lo = m1 + 1 } else { hi = m2 - 1 }
}
best := lo
for i := lo + 1; i <= hi; i++ { if f(i) > f(best) { best = i } }
return best
}
Java:
int ternaryMax(int lo, int hi, java.util.function.IntUnaryOperator f) {
while (hi - lo > 2) {
int m1 = lo + (hi - lo) / 3;
int m2 = hi - (hi - lo) / 3;
if (f.applyAsInt(m1) < f.applyAsInt(m2)) lo = m1 + 1;
else hi = m2 - 1;
}
int best = lo;
for (int i = lo + 1; i <= hi; i++) if (f.applyAsInt(i) > f.applyAsInt(best)) best = i;
return best;
}
Task 15 — Parallel Binary Search Across Multiple Keys¶
Problem. Given a sorted array a[] and a list of q queries targets[], return all answers efficiently. Naive: q * log(n). Use a sort + merge approach for total O((n + q) log q).
Idea: sort the queries, then walk both arrays once with a two-pointer merge. For each query in sorted order, advance the array pointer until you cross the target, then record the answer. This is the batched binary search trick.
Python:
def batch_search(a, targets):
"""For each target, return its lower_bound index in a."""
n, q = len(a), len(targets)
order = sorted(range(q), key=lambda i: targets[i])
answers = [0] * q
ai = 0
for qi in order:
t = targets[qi]
while ai < n and a[ai] < t:
ai += 1
answers[qi] = ai
return answers
Go:
func batchSearch(a, targets []int) []int {
n, q := len(a), len(targets)
order := make([]int, q)
for i := range order { order[i] = i }
sort.Slice(order, func(i, j int) bool { return targets[order[i]] < targets[order[j]] })
answers := make([]int, q)
ai := 0
for _, qi := range order {
for ai < n && a[ai] < targets[qi] { ai++ }
answers[qi] = ai
}
return answers
}
Java:
int[] batchSearch(int[] a, int[] targets) {
int n = a.length, q = targets.length;
Integer[] order = new Integer[q];
for (int i = 0; i < q; i++) order[i] = i;
java.util.Arrays.sort(order, (x, y) -> targets[x] - targets[y]);
int[] answers = new int[q];
int ai = 0;
for (int qi : order) {
while (ai < n && a[ai] < targets[qi]) ai++;
answers[qi] = ai;
}
return answers;
}
Complexity: O((n + q) log q). Beats O(q log n) when q >> log n.
Self-Check¶
After completing these tasks, you should be able to:
- Write classic binary search from memory in any of Go / Java / Python.
- Choose between inclusive
[lo, hi]and half-open[lo, hi)based on the problem. - Implement
lower_bound/upper_boundand explain the<vs<=difference. - Recognize parametric search problems and write the predicate.
- Apply binary search to floats with a fixed iteration count.
- Use exponential search for unbounded ranges.
- Use ternary search for unimodal functions.
- Identify when binary search is the wrong tool (linked lists, unsorted data, exact-match-only).
If any of these feel uncertain, revisit the relevant section of junior.md or middle.md and redo the task without looking at the solution.