Chapter 16: Searching Algorithms
16.04Beginner / Intermediate6 topics25 min4 interactive animations

Binary search over answer

Some problems ask for the smallest or largest value that satisfies a condition, rather than the position of a value. If the condition is monotonic, you can binary search the answer itself and test each guess with a fast greedy check.

Learning goals

  • Recognise a monotonic feasibility predicate
  • Pick the answer range [lo, hi] and the right mid rounding
  • Solve minimise-the-maximum and maximise-the-minimum problems
  • Write the greedy feasibility check for each classic problem

Prerequisites

Lower boundGreedy reasoningPrefix sums are helpful

“What is the slowest speed that still finishes on time?” Trying every speed works but is slow. If speed s works, every faster speed also works, so you can binary search the speed.

16.04.1

Monotonic feasibility

A predicate ok(x) is monotonic if once it becomes true it stays true (false, false, true, true, …), or the reverse. Binary search then finds the boundary in O(log range) calls to ok.

Template for “smallest x that works”: if ok(mid), set hi = mid; otherwise lo = mid + 1. For “largest x that works”, round mid up and set lo = mid or hi = mid - 1. Rounding up prevents an infinite loop when hi = lo + 1.

Java
1// smallest x in [lo, hi] with ok(x) == true
2while (lo < hi) {
3 int mid = lo + (hi - lo) / 2;
4 if (ok(mid)) hi = mid;
5 else lo = mid + 1;
6}
7return lo;
8
9// largest x in [lo, hi] with ok(x) == true
10while (lo < hi) {
11 int mid = lo + (hi - lo + 1) / 2; // round up
12 if (ok(mid)) lo = mid;
13 else hi = mid - 1;
14}
15return lo;

Worst

O(log(range) × cost of ok)

Space

O(1)

16.04.2

Minimum possible maximum

“Split the work so the largest share is as small as possible” is the most common answer-search shape. If a limit L is achievable, any larger limit is too, so the predicate is monotonic.

The answer lies between the largest single item (no share can be smaller) and the total (one share takes everything). The check greedily fills each share up to L and counts how many shares it needs.

  • Book allocation, ship capacity and split-array-largest-sum all have this shape.
  • The greedy check is O(n), so the whole solution is O(n log(sum)).
16.04.3

Allocate books

Given pages per book in order and k students, give each student a contiguous block of at least one book and minimise the maximum pages any student reads. If there are more students than books, there is no valid allocation.

The animation shows each feasibility check by colouring the books by the student who reads them.

Allocate minimum pages (book allocation)

Interactive · edit the input

Your input

Try

Answer space [90, 203]

90203

Input

12346790
Remaining windowKnown to workKnown to fail
Setup

Split the books into 2 contiguous parts and minimise the largest part. Every answer lies in [90, 203], and feasibility is monotonic, so binary search the answer itself.

Step 1 / 31
Java
1public static int allocateBooks(int[] pages, int students) {
2 if (students > pages.length) return -1;
3 int lo = max(pages), hi = sum(pages);
4 while (lo < hi) {
5 int mid = lo + (hi - lo) / 2;
6 if (studentsNeeded(pages, mid) <= students) {
7 hi = mid;
8 } else {
9 lo = mid + 1;
10 }
11 }
12 return lo;
13}
14
15static int studentsNeeded(int[] pages, int limit) {
16 int need = 1, sum = 0;
17 for (int p : pages) {
18 if (sum + p > limit) { need++; sum = 0; }
19 sum += p;
20 }
21 return need;
22}

Live state

0 feasibility checks

k

2

lo

90

hi

203

No condition evaluated on this step
Execution trace (dry run table)Show 31 steps
StepLineStateWhat happens
1L3k=2 lo=90 hi=203Split the books into 2 contiguous parts and minimise the largest part. Every answer lies in [90, 203], and feasibility is monotonic, so binary search the answer itself.
2L4k=2 lo=90 hi=203114 candidate answers remain.
3L5k=2 lo=90 hi=203 mid=146mid = 146.
4L6k=2 lo=90 hi=203 mid=146146 works. Every larger value works too, so try smaller.
5L7k=2 lo=90 hi=146hi = 146.
6L4k=2 lo=90 hi=14657 candidate answers remain.
7L5k=2 lo=90 hi=146 mid=118mid = 118.
8L6k=2 lo=90 hi=146 mid=118118 works. Every larger value works too, so try smaller.
9L7k=2 lo=90 hi=118hi = 118.
10L4k=2 lo=90 hi=11829 candidate answers remain.
11L5k=2 lo=90 hi=118 mid=104mid = 104.
12L6k=2 lo=90 hi=118 mid=104104 fails. Every smaller value fails too.
13L9k=2 lo=105 hi=118lo = 105.
14L4k=2 lo=105 hi=11814 candidate answers remain.
15L5k=2 lo=105 hi=118 mid=111mid = 111.
16L6k=2 lo=105 hi=118 mid=111111 fails. Every smaller value fails too.
17L9k=2 lo=112 hi=118lo = 112.
18L4k=2 lo=112 hi=1187 candidate answers remain.
19L5k=2 lo=112 hi=118 mid=115mid = 115.
20L6k=2 lo=112 hi=118 mid=115115 works. Every larger value works too, so try smaller.
21L7k=2 lo=112 hi=115hi = 115.
22L4k=2 lo=112 hi=1154 candidate answers remain.
23L5k=2 lo=112 hi=115 mid=113mid = 113.
24L6k=2 lo=112 hi=115 mid=113113 works. Every larger value works too, so try smaller.
25L7k=2 lo=112 hi=113hi = 113.
26L4k=2 lo=112 hi=1132 candidate answers remain.
27L5k=2 lo=112 hi=113 mid=112mid = 112.
28L6k=2 lo=112 hi=113 mid=112112 fails. Every smaller value fails too.
29L9k=2 lo=113 hi=113lo = 113.
30L4k=2 lo=113 hi=113lo == hi == 113. The answer is pinned down.
31L12k=2 return=113return 113. 2 students <= 2. Found with 7 checks instead of testing all 114 values.
16.04.4

Aggressive cows

Place c cows in stalls at given positions so the minimum distance between any two cows is as large as possible. This is the maximise-the-minimum mirror of the previous problems.

After sorting, greedily place a cow in the first stall, then in each next stall that is at least gap away. If that fits c cows, gap works and so does every smaller gap.

Aggressive cows (maximise the minimum distance)

Interactive · edit the input

Your input

Try
Setup

Sort the positions so the greedy check can walk left to right.

Step 1 / 16
Java
1public static int aggressiveCows(int[] stalls, int cows) {
2 Arrays.sort(stalls);
3 int lo = 1, hi = stalls[stalls.length - 1] - stalls[0];
4 while (lo < hi) {
5 int mid = lo + (hi - lo + 1) / 2; // round up
6 if (canPlace(stalls, cows, mid)) {
7 lo = mid;
8 } else {
9 hi = mid - 1;
10 }
11 }
12 return lo;
13}
14
15static boolean canPlace(int[] stalls, int cows, int gap) {
16 int placed = 1, last = stalls[0];
17 for (int i = 1; i < stalls.length; i++) {
18 if (stalls[i] - last >= gap) { placed++; last = stalls[i]; }
19 }
20 return placed >= cows;
21}

Live state

0 feasibility checks

k

3

No condition evaluated on this step
Execution trace (dry run table)Show 16 steps
StepLineStateWhat happens
1L2k=3Sort the positions so the greedy check can walk left to right.
2L3k=3 lo=1 hi=8Place 3 cows so the closest pair is as far apart as possible. Every answer lies in [1, 8], and feasibility is monotonic, so binary search the answer itself.
3L4k=3 lo=1 hi=88 candidate answers remain.
4L5k=3 lo=1 hi=8 mid=5mid = 5. Round up so lo = mid always makes progress.
5L6k=3 lo=1 hi=8 mid=55 fails. Every larger value fails too.
6L9k=3 lo=1 hi=4hi = 4.
7L4k=3 lo=1 hi=44 candidate answers remain.
8L5k=3 lo=1 hi=4 mid=3mid = 3. Round up so lo = mid always makes progress.
9L6k=3 lo=1 hi=4 mid=33 works. Every smaller value works too, so try larger.
10L7k=3 lo=3 hi=4lo = 3.
11L4k=3 lo=3 hi=42 candidate answers remain.
12L5k=3 lo=3 hi=4 mid=4mid = 4. Round up so lo = mid always makes progress.
13L6k=3 lo=3 hi=4 mid=44 fails. Every larger value fails too.
14L9k=3 lo=3 hi=3hi = 3.
15L4k=3 lo=3 hi=3lo == hi == 3. The answer is pinned down.
16L12k=3 return=3return 3. 3 cows fit >= 3. Found with 3 checks instead of testing all 8 values.
16.04.5

Capacity to ship

Packages must ship in their given order within D days. Find the smallest ship capacity that makes this possible.

The answer lies between the heaviest package and the total weight. The check loads each day until the next package would overflow, then starts a new day.

Capacity to ship packages within D days

Interactive · edit the input

Your input

Try

Answer space [10, 55]

1055

Input

12345678910
Remaining windowKnown to workKnown to fail
Setup

Find the smallest ship capacity that delivers every package, in order, within 5 days. Every answer lies in [10, 55], and feasibility is monotonic, so binary search the answer itself.

Step 1 / 23
Java
1public static int shipWithinDays(int[] weights, int days) {
2 int lo = max(weights), hi = sum(weights);
3 while (lo < hi) {
4 int mid = lo + (hi - lo) / 2;
5 if (daysNeeded(weights, mid) <= days) {
6 hi = mid;
7 } else {
8 lo = mid + 1;
9 }
10 }
11 return lo;
12}
13
14static int daysNeeded(int[] weights, int cap) {
15 int need = 1, load = 0;
16 for (int w : weights) {
17 if (load + w > cap) { need++; load = 0; } // start a new day
18 load += w;
19 }
20 return need;
21}

Live state

0 feasibility checks

k

5

lo

10

hi

55

No condition evaluated on this step
Execution trace (dry run table)Show 23 steps
StepLineStateWhat happens
1L2k=5 lo=10 hi=55Find the smallest ship capacity that delivers every package, in order, within 5 days. Every answer lies in [10, 55], and feasibility is monotonic, so binary search the answer itself.
2L3k=5 lo=10 hi=5546 candidate answers remain.
3L4k=5 lo=10 hi=55 mid=32mid = 32.
4L5k=5 lo=10 hi=55 mid=3232 works. Every larger value works too, so try smaller.
5L6k=5 lo=10 hi=32hi = 32.
6L3k=5 lo=10 hi=3223 candidate answers remain.
7L4k=5 lo=10 hi=32 mid=21mid = 21.
8L5k=5 lo=10 hi=32 mid=2121 works. Every larger value works too, so try smaller.
9L6k=5 lo=10 hi=21hi = 21.
10L3k=5 lo=10 hi=2112 candidate answers remain.
11L4k=5 lo=10 hi=21 mid=15mid = 15.
12L5k=5 lo=10 hi=21 mid=1515 works. Every larger value works too, so try smaller.
13L6k=5 lo=10 hi=15hi = 15.
14L3k=5 lo=10 hi=156 candidate answers remain.
15L4k=5 lo=10 hi=15 mid=12mid = 12.
16L5k=5 lo=10 hi=15 mid=1212 fails. Every smaller value fails too.
17L8k=5 lo=13 hi=15lo = 13.
18L3k=5 lo=13 hi=153 candidate answers remain.
19L4k=5 lo=13 hi=15 mid=14mid = 14.
20L5k=5 lo=13 hi=15 mid=1414 fails. Every smaller value fails too.
21L8k=5 lo=15 hi=15lo = 15.
22L3k=5 lo=15 hi=15lo == hi == 15. The answer is pinned down.
23L11k=5 return=15return 15. 5 days <= 5. Found with 5 checks instead of testing all 46 values.
16.04.6

Koko eating bananas

Koko eats at most k bananas per hour from one pile and must finish all piles within h hours. Find the minimum k.

A pile of p bananas takes ceil(p / k) hours, written in integer maths as (p + k - 1) / k. Feasibility is monotonic in k, and k never needs to exceed the largest pile.

Koko eating bananas

Interactive · edit the input

Your input

Try

Answer space [1, 11]

111

Input

36711
Remaining windowKnown to workKnown to fail
Setup

Find the slowest eating speed that finishes all piles within 8 hours. Every answer lies in [1, 11], and feasibility is monotonic, so binary search the answer itself.

Step 1 / 19
Java
1public static int minEatingSpeed(int[] piles, int h) {
2 int lo = 1, hi = Arrays.stream(piles).max().getAsInt();
3 while (lo < hi) {
4 int mid = lo + (hi - lo) / 2;
5 if (hoursNeeded(piles, mid) <= h) {
6 hi = mid;
7 } else {
8 lo = mid + 1;
9 }
10 }
11 return lo;
12}
13
14static long hoursNeeded(int[] piles, int k) {
15 long hours = 0;
16 for (int p : piles) hours += (p + k - 1) / k; // ceil(p / k)
17 return hours;
18}

Live state

0 feasibility checks

k

8

lo

1

hi

11

No condition evaluated on this step
Execution trace (dry run table)Show 19 steps
StepLineStateWhat happens
1L2k=8 lo=1 hi=11Find the slowest eating speed that finishes all piles within 8 hours. Every answer lies in [1, 11], and feasibility is monotonic, so binary search the answer itself.
2L3k=8 lo=1 hi=1111 candidate answers remain.
3L4k=8 lo=1 hi=11 mid=6mid = 6.
4L5k=8 lo=1 hi=11 mid=66 works. Every larger value works too, so try smaller.
5L6k=8 lo=1 hi=6hi = 6.
6L3k=8 lo=1 hi=66 candidate answers remain.
7L4k=8 lo=1 hi=6 mid=3mid = 3.
8L5k=8 lo=1 hi=6 mid=33 fails. Every smaller value fails too.
9L8k=8 lo=4 hi=6lo = 4.
10L3k=8 lo=4 hi=63 candidate answers remain.
11L4k=8 lo=4 hi=6 mid=5mid = 5.
12L5k=8 lo=4 hi=6 mid=55 works. Every larger value works too, so try smaller.
13L6k=8 lo=4 hi=5hi = 5.
14L3k=8 lo=4 hi=52 candidate answers remain.
15L4k=8 lo=4 hi=5 mid=4mid = 4.
16L5k=8 lo=4 hi=5 mid=44 works. Every larger value works too, so try smaller.
17L6k=8 lo=4 hi=4hi = 4.
18L3k=8 lo=4 hi=4lo == hi == 4. The answer is pinned down.
19L11k=8 return=4return 4. 8h <= 8h. Found with 4 checks instead of testing all 11 values.

Practice ladder

Now try it yourself

Easy

  • Square root of an integer (floor)
  • First bad version

Medium

  • Koko eating bananas
  • Capacity to ship packages within D days
  • Minimum days to make m bouquets

Advanced

  • Split array largest sum
  • Aggressive cows
  • Painter's partition
  • Kth smallest number in a multiplication table

Key takeaways

  • If ok(x) is monotonic, binary search the answer instead of the array.
  • Bound the answer tightly, write an O(n) greedy check, and choose mid rounding to match the update rule.
  • Total cost is O(n log(range)).