196 lines
10 KiB
YAML
196 lines
10 KiB
YAML
title: Capacity To Ship Packages Within D Days
|
|
slug: capacity-to-ship-packages-within-d-days
|
|
difficulty: medium
|
|
leetcode_id: 1011
|
|
leetcode_url: https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/
|
|
categories:
|
|
- arrays
|
|
- binary-search
|
|
patterns:
|
|
- binary-search
|
|
|
|
description: |
|
|
A conveyor belt has packages that must be shipped from one port to another within `days` days.
|
|
|
|
The i<sup>th</sup> package on the conveyor belt has a weight of `weights[i]`. Each day, we load the ship with packages on the conveyor belt (in the order given by `weights`). We may not load more weight than the maximum weight capacity of the ship.
|
|
|
|
Return *the least weight capacity of the ship that will result in all the packages on the conveyor belt being shipped within* `days` *days*.
|
|
|
|
constraints: |
|
|
- `1 <= days <= weights.length <= 5 * 10^4`
|
|
- `1 <= weights[i] <= 500`
|
|
|
|
examples:
|
|
- input: "weights = [1,2,3,4,5,6,7,8,9,10], days = 5"
|
|
output: "15"
|
|
explanation: "A ship capacity of 15 is the minimum to ship all packages in 5 days: Day 1: [1,2,3,4,5], Day 2: [6,7], Day 3: [8], Day 4: [9], Day 5: [10]. The cargo must be shipped in order, so rearranging packages is not allowed."
|
|
- input: "weights = [3,2,2,4,1,4], days = 3"
|
|
output: "6"
|
|
explanation: "A ship capacity of 6 ships all packages in 3 days: Day 1: [3,2], Day 2: [2,4], Day 3: [1,4]."
|
|
- input: "weights = [1,2,3,1,1], days = 4"
|
|
output: "3"
|
|
explanation: "A ship capacity of 3 ships all packages in 4 days: Day 1: [1], Day 2: [2], Day 3: [3], Day 4: [1,1]."
|
|
|
|
explanation:
|
|
intuition: |
|
|
This problem asks us to find the **minimum ship capacity** that allows us to ship all packages within a given number of days. The key insight is recognising that ship capacity has a **monotonic property** with respect to the number of days required.
|
|
|
|
Think of it like this: if a ship with capacity `C` can ship all packages in `D` days, then any ship with capacity greater than `C` can also do it in `D` or fewer days. Conversely, if capacity `C` is insufficient, any smaller capacity will also be insufficient. This monotonicity screams **binary search**.
|
|
|
|
Imagine you're adjusting a dial that controls ship capacity. Turn it too low, and you need too many days. Turn it too high, and you waste capacity. The "sweet spot" is the minimum capacity where you just barely fit within the day limit — and binary search efficiently finds that boundary.
|
|
|
|
The search space for capacity ranges from `max(weights)` (you must fit the heaviest single package) to `sum(weights)` (ship everything in one day). Binary search narrows this range by testing the midpoint and deciding which half contains the answer.
|
|
|
|
approach: |
|
|
We solve this using **Binary Search on the Answer**:
|
|
|
|
**Step 1: Define the search space**
|
|
|
|
- `left`: Set to `max(weights)` — the ship must at least carry the heaviest package
|
|
- `right`: Set to `sum(weights)` — this capacity ships everything in one day
|
|
|
|
|
|
|
|
**Step 2: Binary search for minimum valid capacity**
|
|
|
|
- Calculate `mid = (left + right) // 2` as a candidate capacity
|
|
- Check if this capacity can ship all packages within `days` days using a helper function
|
|
- If yes, the answer might be `mid` or smaller, so search left: `right = mid`
|
|
- If no, we need more capacity, so search right: `left = mid + 1`
|
|
|
|
|
|
|
|
**Step 3: Implement the feasibility check**
|
|
|
|
- Simulate loading the ship day by day
|
|
- Greedily load packages onto the current day until adding another would exceed capacity
|
|
- When exceeded, start a new day and reset the current load
|
|
- Count total days required and compare with the limit
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- When `left == right`, we've found the minimum valid capacity
|
|
- Return `left`
|
|
|
|
|
|
|
|
This approach works because the feasibility function is monotonic: once a capacity works, all larger capacities also work. Binary search exploits this to find the boundary in O(log(sum - max)) iterations.
|
|
|
|
common_pitfalls:
|
|
- title: Wrong Search Space Boundaries
|
|
description: |
|
|
A common mistake is setting `left = 1` or `left = 0`. The minimum possible capacity must be at least `max(weights)` — otherwise, the heaviest package cannot be loaded at all.
|
|
|
|
Similarly, setting `right` too high (like `10^9`) wastes iterations. The maximum useful capacity is `sum(weights)`, which ships everything in one day.
|
|
wrong_approach: "left = 1 or left = 0"
|
|
correct_approach: "left = max(weights), right = sum(weights)"
|
|
|
|
- title: Off-by-One in Binary Search
|
|
description: |
|
|
Binary search on the answer requires careful handling of the search condition. Using `left < right` with `right = mid` finds the leftmost valid value. Using `left <= right` with different updates can cause infinite loops.
|
|
|
|
The pattern `while left < right` with `right = mid` when feasible and `left = mid + 1` when not feasible correctly converges to the minimum valid capacity.
|
|
wrong_approach: "Mixing incompatible loop conditions and updates"
|
|
correct_approach: "Use left < right with right = mid and left = mid + 1"
|
|
|
|
- title: Greedy Simulation Error
|
|
description: |
|
|
When simulating, you might forget to start with `days_needed = 1`. The first package goes on day 1, so you start counting from 1, not 0.
|
|
|
|
Also, when a package doesn't fit on the current day, you must start a new day *and* add that package to it — don't skip the package.
|
|
wrong_approach: "Starting days_needed = 0 or forgetting to add the package after starting a new day"
|
|
correct_approach: "Start with days_needed = 1 and current_load = 0, then handle day transitions carefully"
|
|
|
|
- title: Linear Search Instead of Binary Search
|
|
description: |
|
|
Testing capacities one by one from `max(weights)` upward works but is O(n * (sum - max)), which can be up to O(n * n * max_weight). With `n = 5 * 10^4` and `weights[i] <= 500`, this could mean billions of operations — guaranteed TLE.
|
|
|
|
Binary search reduces the capacity search from O(sum - max) to O(log(sum - max)), making the total complexity O(n * log(sum)).
|
|
wrong_approach: "for capacity in range(max(weights), sum(weights) + 1)"
|
|
correct_approach: "Binary search on capacity with O(log(sum)) iterations"
|
|
|
|
key_takeaways:
|
|
- "**Binary search on the answer**: When asked to find the minimum/maximum value that satisfies a condition, check if the condition is monotonic — if so, binary search applies"
|
|
- "**Feasibility check pattern**: The helper function that checks 'can we achieve X with value Y?' is the core of binary search on answer problems"
|
|
- "**Search space boundaries matter**: Always identify the tightest valid range — here, `[max(weights), sum(weights)]`"
|
|
- "**Related problems**: This pattern appears in Koko Eating Bananas, Split Array Largest Sum, and Minimum Number of Days to Make m Bouquets"
|
|
|
|
time_complexity: "O(n * log(sum(weights))). Binary search runs O(log(sum - max)) iterations, and each feasibility check scans all n packages."
|
|
space_complexity: "O(1). We only use a constant number of variables for tracking capacity, days, and current load."
|
|
|
|
solutions:
|
|
- approach_name: Binary Search on Answer
|
|
is_optimal: true
|
|
code: |
|
|
def ship_within_days(weights: list[int], days: int) -> int:
|
|
def can_ship(capacity: int) -> bool:
|
|
"""Check if we can ship all packages within 'days' using this capacity."""
|
|
days_needed = 1
|
|
current_load = 0
|
|
|
|
for weight in weights:
|
|
# If adding this package exceeds capacity, start a new day
|
|
if current_load + weight > capacity:
|
|
days_needed += 1
|
|
current_load = 0
|
|
|
|
current_load += weight
|
|
|
|
return days_needed <= days
|
|
|
|
# Search space: minimum is max single package, maximum ships all in one day
|
|
left = max(weights)
|
|
right = sum(weights)
|
|
|
|
# Binary search for minimum valid capacity
|
|
while left < right:
|
|
mid = (left + right) // 2
|
|
|
|
if can_ship(mid):
|
|
# This capacity works, but maybe we can do better
|
|
right = mid
|
|
else:
|
|
# Need more capacity
|
|
left = mid + 1
|
|
|
|
return left
|
|
explanation: |
|
|
**Time Complexity:** O(n * log(sum(weights))) — Binary search over the capacity range, with O(n) feasibility check per iteration.
|
|
|
|
**Space Complexity:** O(1) — Only constant extra space used.
|
|
|
|
We binary search on the ship capacity. For each candidate capacity, we greedily simulate shipping: load packages day by day until one doesn't fit, then start a new day. If the total days needed is within the limit, the capacity is feasible. We find the minimum feasible capacity.
|
|
|
|
- approach_name: Linear Search
|
|
is_optimal: false
|
|
code: |
|
|
def ship_within_days(weights: list[int], days: int) -> int:
|
|
def can_ship(capacity: int) -> bool:
|
|
"""Check if we can ship all packages within 'days' using this capacity."""
|
|
days_needed = 1
|
|
current_load = 0
|
|
|
|
for weight in weights:
|
|
if current_load + weight > capacity:
|
|
days_needed += 1
|
|
current_load = 0
|
|
|
|
current_load += weight
|
|
|
|
return days_needed <= days
|
|
|
|
# Try each capacity starting from minimum possible
|
|
for capacity in range(max(weights), sum(weights) + 1):
|
|
if can_ship(capacity):
|
|
return capacity
|
|
|
|
return sum(weights) # Fallback (always works)
|
|
explanation: |
|
|
**Time Complexity:** O(n * (sum(weights) - max(weights))) — Linear search over capacities, each with O(n) check.
|
|
|
|
**Space Complexity:** O(1) — Only constant extra space used.
|
|
|
|
This brute force approach tests every capacity from `max(weights)` upward until finding one that works. While correct, it's far too slow for large inputs. With `n = 5 * 10^4` and sum up to `2.5 * 10^7`, this could require billions of operations. Included to illustrate why binary search is essential.
|