Chapter 7: Foundational Problem-Solving Patterns
7.07Beginner5 topics12 min4 interactive animations

Counting and searching answer

When a problem asks for the smallest or largest value that makes something possible, and the possible values are ordered, you can search over the answer itself. This lesson collects the pattern; the full treatment with animations is in Chapter 16.

Learning goals

  • Write a feasibility check for a candidate answer
  • Recognise a monotone predicate and its boundary
  • Combine binary search on the answer with a greedy check
  • Use a fixed number of iterations for real-valued answers

Prerequisites

Binary searchGreedy reasoning

“What is the slowest speed that still gets me there on time?” You can test any speed quickly, and if a speed works every faster one works too. That is all binary search needs.

7.07.1

Feasibility check

Split the problem into two parts: “is answer x achievable?” and “which x is optimal?”. The first is often a simple greedy pass; the second is a search over x.

A good check is fast (usually O(n)), returns a plain boolean, and does not try to find the optimum itself. In the Koko animation below, each check computes the hours needed at one speed.

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.
7.07.2

Monotone predicate

A predicate ok(x) is monotone if it is false, false, …, then true, true, … (or the reverse). Then exactly one boundary separates the two, and binary search finds it in O(log range) checks.

Always argue monotonicity before searching. If a larger capacity can ever make a schedule infeasible, binary search returns a wrong answer without any error.

ProblemPredicate ok(x)Direction
Koko eating bananasfinishes within h hours at speed xfalse… then true…
Ship within D dayscapacity x ships everything in D daysfalse… then true…
Aggressive cowscows fit with minimum gap xtrue… then false…
7.07.3

Binary search on answer

Pick bounds [lo, hi] that certainly contain the answer, then run lower-bound binary search on ok. For the smallest feasible x: if ok(mid) set hi = mid, otherwise lo = mid + 1.

Total cost is O(log(hi − lo)) feasibility checks. The animation for capacity to ship packages shows the remaining window shrink and the known-good and known-bad regions grow.

Java
1int lo = minPossible, hi = maxPossible;
2while (lo < hi) {
3 int mid = lo + (hi - lo) / 2;
4 if (ok(mid)) hi = mid; // mid works: the answer is mid or smaller
5 else lo = mid + 1; // mid fails: the answer is larger
6}
7return lo;

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.
7.07.4

Greedy feasibility

The check is usually greedy: fill a day, a student or a container as much as allowed, then start the next one, and count how many you used. Greedy is correct here because filling as much as possible never hurts the later groups.

For “maximise the minimum gap” (aggressive cows), place each item as early as the gap allows. Placing earlier leaves more room for the rest.

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.

Practice ladder

Now try it yourself

Easy

  • Sqrt(x) by binary search
  • First bad version

Medium

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

Advanced

  • Split array largest sum
  • Magnetic force between two balls
  • Minimize max distance to gas station

Key takeaways

  • Separate “is x feasible?” from “which x is best?”.
  • Prove the predicate is monotone, bound the answer, then binary search it.
  • Greedy checks make each probe O(n); real-valued answers use a fixed iteration count.