The Shipping Dock

You run a shipping dock. Ten packages sit on the conveyor belt, weights [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], and they must go out in order — no rearranging. That ordering constraint is crucial: you cannot cherry-pick light packages to balance the load. You must take them in sequence.

Your fleet has 5 days. Every ship you charter has the same capacity. Too small and you cannot finish in time. Too large and you are wasting money on empty cargo holds.

The smallest possible capacity is 10 — the heaviest single package, because each package must fit on a single ship. The largest you would ever need is 55 — loading everything onto one ship in one day. Somewhere in that range is the sweet spot: the minimum capacity that still gets all packages shipped within 5 days.

That is 46 possible capacities to test. You could try them all — test capacity 10, then 11, then 12, and so on until you find the first one that works. That is linear search on the answer space rather than on an array. For 46 candidates it finishes quickly. But what if the range were [1, 10^9]? A billion candidates cannot be tested one by one.

The key question is not “which capacity is correct” — it is “does the set of feasible capacities have a structure you can exploit?” If it does, you might be able to search much faster than one at a time.

Test Capacities

Start testing capacities. For each one, predict whether it will be enough — then watch the packages get loaded day by day.

As you test, pay attention to the pattern that emerges. Does feasibility flip back and forth unpredictably? Or does it have a structure you can exploit?

Ship Packages in D Days

ProbeDiscoverBuildSearch
1 / 4
A conveyor belt has packages with weights 12345678910. Find the MINIMUM ship capacity to ship ALL packages within 5 days. Packages must be shipped in the order they appear.

Type a capacity to test (range: 1055)

The feasibility check works like this: start loading packages onto the first ship. When the next package would exceed the ship's capacity, start a new day. If you run out of days before running out of packages, the capacity is too small.

1
function canShip(weights: number[], cap: number, days: number): boolean {
2
  let daysNeeded = 1;
3
  let currentLoad = 0;
4
  for (const w of weights) {
5
    if (currentLoad + w > cap) {
6
      daysNeeded++;
7
      currentLoad = 0;
8
    }
9
    currentLoad += w;
10
  }
11
  return daysNeeded <= days;
12
}

For capacity 15: day 1 ships [1,2,3,4,5] (sum 15), day 2 ships [6,7] (sum 13), day 3 ships [8], day 4 ships [9], day 5 ships [10]. Five days, just barely enough. For capacity 14: day 1 ships [1,2,3,4] (sum 10), day 2 ships [5,6] (sum 11), day 3 ships [7], day 4 ships [8], day 5 ships [9] — but 10 has no day left. The boundary is exactly at 15.

Search on Answer

You saw the pattern yourself: every capacity below 15 failed, and every capacity at 15 or above worked. No zigzag — just a clean FFFF...TTTT boundary.

That pattern is called a monotonic predicate. The feasibility check canShip(capacity) can only flip from false to true once, because a bigger ship can always carry at least as much as a smaller one. If capacity 15 is enough, then capacity 16 is definitely enough too — you just leave a little empty space.

And a single-boundary pattern is exactly what binary search finds. You were not searching a sorted array — you were searching the answer space [10, 55]. The “comparison” was not arr[mid] vs target — it was the greedy feasibility check you built from code tiles.

1
// Binary search on the answer space
2
let lo = 10, hi = 55;           // range of possible answers
3
while (lo < hi) {
4
  const mid = lo + Math.floor((hi - lo) / 2);
5
  if (canShip(weights, mid, 5)) {
6
    hi = mid;                    // feasible — try smaller
7
  } else {
8
    lo = mid + 1;                // infeasible — need bigger
9
  }
10
}
11
return lo;  // minimum feasible capacity

This technique is called search on answer: binary search over possible answers, not array elements. Any time you see “minimize the maximum” or “find the smallest X such that Y,” check whether the predicate is monotonic. If the pattern is FFFF...TTTT, binary search finds the boundary in log(n) probes.

The recipe has three steps, and they are the same every time:

  1. Identify the answer space. What is the range of possible answers? For shipping, it is [max(weights), sum(weights)]. For Koko (LC 875), it is [1, max(piles)]. For Split Array (LC 410), it is [max(nums), sum(nums)].

  2. Write the feasibility predicate. Given a candidate answer, can you verify it in O(n) or O(n log n)? The predicate must be monotonic: if candidate X works, then X + 1 works too. For shipping, the predicate is greedy packing. For Koko, it is "can she eat all bananas at speed k within h hours?"

  3. Binary search the boundary. Use the standard boundary template (lo < hi, hi = mid) with the predicate replacing the array comparison.

1
// Generic search-on-answer skeleton
2
let lo = MIN_POSSIBLE, hi = MAX_POSSIBLE;
3
while (lo < hi) {
4
  const mid = lo + Math.floor((hi - lo) / 2);
5
  if (isFeasible(mid)) hi = mid;   // feasible → try smaller
6
  else lo = mid + 1;                // infeasible → need bigger
7
}
8
return lo;  // minimum feasible answer

The shipping problem is LC 1011. Koko Eating Bananas (LC 875) and Split Array Largest Sum (LC 410) use the exact same skeleton — different feasibility checks, same outer binary search on the answer space. Once you recognize the FFFF...TTTT pattern, the only creative work is writing the predicate. The binary search wrapper is mechanical.