Overnight restock
Find the slowest packing rate that still clears every pallet before the shift ends, by searching the rate rather than the pallets.
A night-shift packer wants to work as slowly as the deadline allows. The array in front of you is not the thing to search — the rate is.
The problem
Pallets of cartons are waiting in a warehouse. A packer works at a fixed rate of
r cartons per hour, and takes one pallet at a time: within an hour they pack
from a single pallet only, so a pallet of 3 cartons at rate 10 still consumes a
whole hour and the leftover capacity is lost.
There are hours hours left in the shift. Find the smallest rate r that
clears every pallet in time. Working slower is less tiring, so the packer wants
the smallest rate that still finishes.
Input. pallets — a list of integers, cartons on each pallet.
hours — hours remaining in the shift.
Output. The smallest integer rate that clears all pallets within hours.
Example.
pallets = [3, 6, 7, 11], hours = 8 -> 4
At rate 4 the pallets take 1, 2, 2 and 3 hours — 8 in total, exactly the shift. At rate 3 they take 1, 2, 3 and 4 — ten hours, too slow.
A second example, where the extra hour changes the answer:
pallets = [30, 11, 23, 4, 20], hours = 5 -> 30
pallets = [30, 11, 23, 4, 20], hours = 6 -> 23
With exactly as many hours as pallets, every pallet must finish in one hour, so the rate has to match the largest pallet.
Constraints.
1 <= len(pallets) <= 10^41 <= pallets[i] <= 10^9len(pallets) <= hours <= 10^9
Hints
Hint 1
You cannot iterate over the pallets to find the answer — the answer is not one of them. What quantity are you actually choosing?
Hint 2
If rate 7 clears the shift, does rate 8? Does rate 6 tell you anything about rate 5?
Hint 3
The rate is bounded below by 1 and above by the largest pallet — above that, extra speed is wasted, because a pallet never takes less than an hour.
Approach
Brute force
Try every rate from 1 upward, and for each one add up the hours. Checking a rate costs O(n); the rate can be as large as 10⁹, so this is O(n · max) — about 10¹³ operations. Correct, and hopelessly slow.
The insight
The predicate "rate r finishes in time" is monotone: once it becomes true it
stays true, so the rates form a sorted boolean array you can binary search.
Packing faster never takes longer, so hours_needed is non-increasing in r.
That makes the rates look like F F F F T T T T, and the answer is the first
T. Monotonicity is the precondition binary search needs, and here it is the
whole reason the technique is legal — nothing is sorted, and nothing needs to be.
The bounds matter too. Rate 1 is always safe as a lower bound. The largest pallet
is a sufficient upper bound: at that rate every pallet finishes in one hour, and
the constraint hours >= len(pallets) guarantees that is enough.
Algorithm
- Set
lo = 1,hi = max(pallets). - While
lo < hi, takemid = (lo + hi) // 2. - Compute the hours needed at
mid, summingceil(cartons / mid)per pallet. - If it fits in the shift, the answer is
midor lower: sethi = mid. - Otherwise
midis too slow: setlo = mid + 1. - When the range collapses,
lois the first rate that works.
Complexity
Time O(n log m), where m is the largest pallet — about 30 iterations of the binary search, each doing one O(n) pass. Space O(1); nothing is allocated beyond a few integers.
Solution
"""Overnight restock — binary search on the packing rate."""
def hours_needed(pallets, rate):
"""Whole hours to clear every pallet at this rate, one pallet at a time."""
return sum((cartons + rate - 1) // rate for cartons in pallets)
def solve(pallets, hours):
# The predicate "rate r clears the shift" is monotone: if r works, r+1 works.
# So search the rate, not the pallets.
lo, hi = 1, max(pallets)
while lo < hi: # invariant: the answer lies in [lo, hi]
mid = (lo + hi) // 2
if hours_needed(pallets, mid) <= hours:
hi = mid # mid works; nothing faster is needed
else:
lo = mid + 1 # mid is too slow
return loThe cases that ran
TESTS = [
(([3, 6, 7, 11], 8), 4),
(([30, 11, 23, 4, 20], 5), 30),
(([30, 11, 23, 4, 20], 6), 23),
(([1], 1), 1),
(([1000000000], 2), 500000000),
(([4, 4, 4, 4], 4), 4),
]Pitfalls
- Integer division truncates.
cartons // rategives 1 hour for a pallet of 7 at rate 4, when it needs 2. Round up with(cartons + rate - 1) // rate. - Setting
hi = mid - 1on success loses the answer. The successfulmidmight be the smallest working rate, so it has to stay in the range:hi = mid. Pairinghi = midwithlo = mid + 1is what makes the loop terminate. - Starting
loat 0 makes the first division aZeroDivisionError. A rate of zero packs nothing, so 1 is the real floor. - Using
sum(pallets) // hoursas an upper bound is wrong: the leftover capacity within each hour means the total is not the binding constraint.
Variants
- First failing build — the same first-true search, on a predicate that is given to you rather than computed.
- Bindery day quota — the same shape, but the check must respect the order of the items.