DSA sheet · Binary Search · Binary search on answer

Capacity to Ship Packages Within D Days

The second problem of the binary search on answer pattern. The input array happens to be sorted in the example, and the teacher warns us not to be fooled by that: we don't search the array at all. We search over the possible ship capacities. The new things in this video are: how to prove the lower limit (it is max(weights), not 1), how to prove the upper limit (sum(weights)), and how to write the "how many days?" counter correctly, including two small bugs she makes and fixes while dry-running it.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Why binary search works at all

Binary search needs a yes/no question whose answers are arranged as all "no" first, then all "yes" (or the other way round), switching only once. Such a question is called monotonic. Then checking the middle tells us which half can't hold the switch point, and we drop that half. A sorted array is one example; a sorted range of numbers we choose is another.

low, high, mid

Why mid = low + (high - low) // 2?

The teacher points out that the way we compute mid depends on the constraints. In Java/C++, (low + high) / 2 can overflow when low and high are both large: the sum goes past the int limit (about 2.1 × 10⁹) and becomes garbage. low + (high - low) / 2 never builds a number bigger than high, so it's safe. Python integers never overflow, so either form works in Python; we keep the safe form as a habit.

The pattern: binary search on answer

  1. Answer range: decide the smallest answer that could possibly work (low) and a value that surely works (high).
  2. Check function: check(x) = "if the answer were x, is it possible?" → True / False.
  3. Monotonic: here, a ship that can carry more never needs more days. So if capacity x works, every bigger capacity also works: no no no yes yes yes.
  4. Which side to keep: we want the minimum capacity = the first yes. Yes at mid → save mid, go left. No at mid → go right.
capacities:  low ......................................... high
check(x):     no   no   no   no  YES  YES  YES  YES  YES
                                 ^
                        the first YES = least capacity
The unique thing about this patternWe never run binary search on the given input. We run it on a range of answers that we pick. Even if the input array is sorted, that's a coincidence, not the reason.

Part A · Brute force: try every capacity from max to sum

LeetCode 1011

1The question in simple words

Packages sit on a conveyor belt in a fixed order. Package i weighs weights[i]. Every day, we load the ship with packages from the front of the belt, in order, as long as the total stays within the ship's capacity (the most weight it can carry in one trip). Then the ship leaves, and the next day we continue from where we stopped.

Find the least capacity so that all packages are shipped within days days.

index0123456789
weights12345678910days = 5 → answer 15

Why 15? With capacity 15:

daypackages loadedtotalwhy it stops there
11, 2, 3, 4, 515adding 6 would make 21 > 15
26, 713adding 8 would make 21 > 15
3888 + 9 = 17 > 15
4999 + 10 = 19 > 15
51010belt empty

5 days ✓, and no smaller capacity manages it in 5 days.

A smaller illustration she uses first: with capacity 5, you can take 1 and 2 (total 3), but not 3 as well (that makes 6 > 5). Next trip, 3 alone, because 3 + 4 = 7 > 5.

2What the constraints tell us

3Intuition

We are asked for a capacity, a number we assume ourselves, not something inside the array. So the plan is: guess a capacity, simulate the belt, count the days. If it takes too many days, the ship is too small; try a bigger one. Start from the smallest sensible capacity and go up. The first capacity that fits in days is the least.

Doubt: the example array 1…10 is sorted. Can't we binary search on it?
→ No. The sorting is a coincidence: the weights could just as well be 3, 4, 5, 4, 9, 6… We are not looking for a weight. We are looking for a capacity, so the binary search (in Part B) runs over capacities, never over the weights.

4Building the logic from examples

The lower limit: why not start at 1 kg?

Try a 1 kg ship. Day 1 it takes the 1. Can it take 2 as well? No (1 + 2 = 3 > 1). Day 2: can it take the 2 kg package at all? No, never, the package alone is heavier than the ship. Same for 3, 4, … 10. With 2 kg the 3…10 packages can never go. With 5 kg, 6…10 can never go. Even 9 kg can't carry the 10.

So whatever the days, the ship must at least be able to lift the heaviest single package. Anything less is not even a candidate.

low = max(weights)Here that's 10. Below this, some package can never be shipped.

Is low itself the answer? Try capacity 10

7 days > 5 days → not possible. Should we make the ship bigger or smaller? A smaller ship takes more days, so we need a bigger one. On the number line of capacities, from a capacity that fails, we move right.

The upper limit: why sum(weights)?

The teacher builds this from the constraint "days can be as small as 1":

A ship of capacity sum(weights) can always ship everything in 1 day, and days ≥ 1. So it always works, and going higher is pointless.

high = sum(weights)Here that's 55. So the answer lies in [10, 55].
Doubt: can high be smaller than low?
→ No. The sum includes the max, so sum ≥ max. With a single package (say 10), both are 10 and the range is just [10, 10].
Doubt: does brute force need high?
→ Not really. Because an answer always exists, a loop going up from low will stop on its own. You only must have an upper limit if it's possible that no answer exists (then something has to stop the loop). For binary search we need both ends anyway.

Writing the day counter (the heart of the problem)

The teacher builds canShip(weights, days, capacity) while dry-running capacity 15. We keep two variables: total (weight loaded on today's ship) and d (days used).

  1. Take packages one by one and add them to total: 1, 3, 6, 10, 15.
  2. Next is 6: 15 + 6 = 21 > 15. This package must go on a new day, so d goes up by one.
  3. And today's load must be reset.
Bug 1 she fixes: reset total = 0 on a new day?
→ That loses the package that didn't fit. The loop moves on to 7, and the 6 is never counted. The 6 is the first package of the new day, so the reset must be total = w (the current package), not 0.

Continuing: day 2 starts with 6, then 6 + 7 = 13, then 13 + 8 = 21 > 15 → new day, total = 8. 8 + 9 = 17 > 15 → new day, total = 9. 9 + 10 = 19 > 15 → new day, total = 10. The loop ends.

Bug 2 she fixes: starting with d = 0 gives 4 days, but the table above shows 5. Why?
→ d only goes up when a package overflows into a new day. The day we were on when the loop started (day 1) was never counted, and the last day (10 alone) is still "open" when the loop ends. Fix: start with d = 1 (we're already on day 1), or keep d = 0 and add 1 after the loop for the last open day. Same thing.

Finally: return d <= days. Using fewer days than allowed is also fine.

Doubt: do we check before adding, or after?
→ In the final code we check before adding: "would total + w go over capacity?" If yes → new day, total = w. If no → total += w. That way total never holds an overweight value.

5Approach steps

  1. low = max(weights), high = sum(weights).
  2. For each capacity cap from low to high: if canShip(cap), return cap.
  3. canShip: d = 1, total = 0. For each w: if total + w > cap → d += 1, total = w; else total += w. Return d ≤ days.

6Code (Python)

Brute force (correct, but TLE)
class Solution:
    def shipWithinDays(self, weights, days):
        low = max(weights)                 # must lift the heaviest package
        high = sum(weights)                # ships everything in 1 day
        for cap in range(low, high + 1):
            if self.canShip(weights, days, cap):
                return cap                 # first that works = least
        return high

    def canShip(self, weights, days, cap):
        d = 1                              # we are on day 1
        total = 0                          # weight on today's ship
        for w in weights:
            if total + w > cap:            # w doesn't fit today
                d += 1                     # start a new day...
                total = w                  # ...with w on board (not 0!)
            else:
                total += w
        return d <= days

7Code line by line

linewhat it means
low = max(weights)Smaller ships can't lift the heaviest package at all.
high = sum(weights)This ship takes everything in one trip, so it always works.
for cap in range(low, high + 1):Try capacities in increasing order.
if self.canShip(...): return capThe first that works is the least, because we go upward.
d = 1Loading starts on day 1 (fixes the "4 instead of 5" bug).
if total + w > cap:Would this package overload today's ship?
d += 1 total = wYes: the ship leaves, a new day begins, and w is its first package.
total += wNo: load it today.
return d <= daysPossible if we used at most the allowed number of days.

8Dry run

weights = 1…10, days = 5. Range [10, 55].

capacitytripsdays used≤ 5 ?
10[1,2,3,4] [5] [6] [7] [8] [9] [10]7no
11[1,2,3,4] [5,6] [7] [8] [9] [10]6no
12[1,2,3,4] [5,6] [7] [8] [9] [10]6no
13[1,2,3,4] [5,6] [7] [8] [9] [10]6no
14[1,2,3,4] [5,6] [7] [8] [9] [10]6no
15[1,2,3,4,5] [6,7] [8] [9] [10]5yes → return 15
capacity :  10  11  12  13  14  15  16  17  ...  55
days     :   7   6   6   6   6   5   5   4  ...   1
<= 5 ?   :  no  no  no  no  no YES YES YES ... YES
                                ^ first YES = 15

9Complexity & remember

Remember the brute forceRange max(weights) … sum(weights). Simulate the belt: overflow → new day, start it with w. Start counting at day 1.

Part B · Optimal: binary search on the capacity

1The question

Same as Part A. Same range, same canShip. Only the outer loop changes.

2Constraints

The range [max, sum] can be up to 2.5 × 10⁷ wide. Halving it needs only about log₂(2.5 × 10⁷) ≈ 25 steps.

3Intuition

The capacities max … sum are whole numbers on a number line, so they're sorted. And "can ship in time?" is no…no yes…yes along that line. So instead of trying them one by one, test the middle capacity and drop half the line. The inner loop (the belt simulation) can't be skipped, since we must look at every package to count the days.

4Building the decisions

She starts ans at 0. That's fine, because sum(weights) always works, so ans is always overwritten at least once.

Doubt: brute force vs optimal, what's really different?
→ Only the outer loop. Whether you go brute force or binary search, you must write the check function yourself, and that's what makes this pattern special. The binary search part is the same template every time.

5Approach steps

  1. low = max(weights), high = sum(weights), ans = 0.
  2. While low <= high: mid = low + (high - low) // 2 (the capacity to test).
  3. If canShip(mid): ans = mid, high = mid - 1.
  4. Else: low = mid + 1.
  5. Return ans.

6Code (Python)

Binary search on answer
class Solution:
    def shipWithinDays(self, weights, days):
        low = max(weights)
        high = sum(weights)
        ans = 0
        while low <= high:
            mid = low + (high - low) // 2      # mid = capacity to test
            if self.canShip(weights, days, mid):
                ans = mid                      # works: remember it
                high = mid - 1                 # try a smaller ship
            else:
                low = mid + 1                  # too small: bigger ship
        return ans

    def canShip(self, weights, days, cap):
        d = 1
        total = 0
        for w in weights:
            if total + w > cap:
                d += 1
                total = w
            else:
                total += w
        return d <= days

7Code line by line

linewhat it means
low = max(weights) high = sum(weights)The answer range from Part A.
ans = 0Placeholder; it will be replaced, since high always works.
while low <= high:Some capacity is still untested.
mid = low + (high - low) // 2The middle capacity (overflow-safe form).
ans = mid high = mid - 1Mid works: save it, then look for a smaller working capacity on the left.
low = mid + 1Mid fails: everything ≤ mid fails too, look right.
return ansThe least capacity that worked.
canShip(...)Exactly the same function as in Part A.

8Dry run (hand table)

weights = 1…10, days = 5. low = 10, high = 55.

steplowhighmidtrips at middayscanShip?decisionthrown away
1105532[1..7] [8,9,10]2yesans = 32, high = 3132 … 55
2103120[1..5] [6,7] [8,9] [10]4yesans = 20, high = 1920 … 31
3101914[1..4] [5,6] [7] [8] [9] [10]6nolow = 1510 … 14
4151917[1..5] [6,7] [8,9] [10]4yesans = 17, high = 1617 … 19
5151615[1..5] [6,7] [8] [9] [10]5yesans = 15, high = 1415 … 16
end1514low > high → return ans = 15 ✓

The range of capacities shrinking (yellow = mid, grey = gone). Only the interesting part 10 … 20 is drawn:

cap1011121314151617181920(21 … 55 already gone after steps 1–2)
step 2101112131415161718192020 works → go left
step 3101112131415161718192014 fails → go right
step 4101112131415161718192017 works → go left
step 5101112131415161718192015 works → ans = 15, nothing left on its left

9Complexity & remember

Remember Ship PackagesSearch the capacity. low = max (lift the heaviest), high = sum (one trip). canShip: day starts at 1; overflow → d += 1; total = w. Works → save, go left.

Part C · Revision page

Brute forceBinary search on answer
rangemax(weights) … sum(weights)same
walkone capacity at a time, upwardtest mid, drop half
checksimulate the belt in order, count days, compare with days
time≈ 2.5 × 10⁷ × 5 × 10⁴ → TLE≈ 25 × 5 × 10⁴ ✓
Koko (problem 14)Ship packages (this one)
answer is a…speedcapacity
low1max(weights): smaller can't lift a package
highmax(piles)sum(weights): one trip takes all
checkΣ ceil(p/k) ≤ hdays counted by the belt simulation ≤ days
order matters?no (any pile order gives the same hours)yes (conveyor belt, can't skip)
If you remember only 5 lines 1. Don't binary search the weights. Binary search the capacity.
2. low = max(weights), high = sum(weights).
3. canShip: d = 1, total = 0; if total + w > cap → d += 1, total = w; else total += w.
4. Works → ans = mid, high = mid − 1. Fails → low = mid + 1.
5. O(n log(sum)) instead of O(n × sum).
Mistakes to avoid ✗ starting low at 1 (the heaviest package can't be shipped)
✗ resetting total = 0 on a new day (the package that overflowed is lost)
✗ starting d = 0 (you miss the last day)
✗ trying to "fill the gap" with a later small package (the belt order is fixed)
✗ binary searching the weights array because it looks sorted
test it yourself (paste under the Part B solution)
s = Solution()
print(s.shipWithinDays([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5))   # 15
print(s.shipWithinDays([3, 2, 2, 4, 1, 4], 3))                # 6
print(s.shipWithinDays([1, 2, 3, 1, 1], 4))                   # 3
print(s.shipWithinDays([2, 7], 1))                            # 9
print(s.shipWithinDays([10], 1))                              # 10

Based on this video: Capacity to Ship Packages Within D Days | Binary Search on Answer