1011. Capacity To Ship Packages Within D Days
1011. Capacity To Ship Packages Within D Days
題目給定一個陣列內含沒有排序的正整數以及一個數字代表天數。
這個陣列的意義是總共有 n 個正整數,每個數字是包裹的重量,排列的順序代表要寄送的順序,天數是我們需要在這個天數內把所有的包裹寄送完。
題目所要求的是找出一個貨櫃,該貨櫃有一定的容量,在規定的天數內分批運送完這些這些貨物,並且我們要找出這個容量的最小值。
這個題目乍看之下很像是需要動態規劃的題目,因為很像是要透過不同的組合去找出一個極值。但是仔細想想後發現動態規劃找不太到動態規劃的轉移方程式。
因為題目有規定包裹要依序寄送,找尋答案的過程中並不存在著子問題的情況,因此動態規劃並不一定適合。
這時候需要多觀察一下題目,首先既然要找出貨櫃的最小容量,代表的是我們應該要用盡所有的天數來寄送所有包裹,可以這樣證明,因為如果還有天數可以寄送,那就代表某一天的包裹可以分拆成兩天,那需要的貨櫃最小容量就可以再更少。
接著這個貨櫃的大小有沒有範圍限制?答案是有的
最小的貨櫃可以有多小呢?最小的貨櫃需要滿足:最少要裝一個貨物,而這個貨物至少要是這些貨物中最重的貨物的重量,因為如果容量比最重的貨物還小,那就會裝不進去這個貨物,就不可能滿足題目的條件。
而最大的貨櫃可以多大呢?最大的貨櫃則是可以把所有的貨物都裝在一起,也就是所有貨物的重量的總和,如果天數剛好是一天內要寄完,那這個貨櫃剛好就裝下所有的東西。
透過以上貨櫃的觀察,我們可以把貨櫃的大小限制在以下的範圍:
$$ max(weights) <= capacity <= sum(weights) $$
我們的目標就是找到 \(capacity \),這時候題目才會比較明朗化,在一個範圍要找到一個滿足條件的目標值,最好的方法其實就是二分搜索,在這個題目其實感覺也很合理,例如:我在這個範圍中間切一刀,接著看看我需要花幾天來搬完所有的東西,如果說我需要花的天數大於題目給的天數,那就代表這個貨櫃的容量太小的,我要再增加我的容量,如果我需要花費的天數少於獲釋等於所需要花費的天數,那就代表容積太大了,我可以往容積更小的方向來搜尋。
至此,題目所欠缺的便是給定貨櫃的容積,如何成功計算出所需要花費的天數了。
這個天數好算是因為題目有給定寄送的方式,那就是每次貨櫃盡量裝,如果裝滿了,那就多加一天,重新裝載。
組合起來後即是二分搜索的變形:
from typing import List
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
# 輔助函式:計算給定承載能力所需天數
def calculate_days(capacity: int) -> int:
total_days = 1 # 至少需要一天
current_load = 0
for weight in weights:
# 如果加上這個包裹會超過容量,則這包裹必須在下一天運送
if current_load + weight > capacity:
current_load = weight
total_days += 1
else:
current_load += weight
return total_days
# 搜尋區間:[最小承載力, 最大承載力]
left = max(weights) # 最小:船至少要能裝最重的包裹
right = sum(weights) # 最大:船能一次裝載所有包裹 (只需 1 天)
# 尋找滿足條件的最小承載力
while left <= right:
mid = left + (right - left) // 2
# 檢查 mid 容量所需的天數
days_needed = calculate_days(mid)
if days_needed > days:
# 容量 mid 太小,導致所需天數太多
# 排除 mid 及其左側
left = mid + 1
else:
# 容量 mid 足夠 (days_needed <= days),mid 可能是答案
# 記錄潛在答案,並嘗試尋找更小的容量
right = mid - 1
# 迴圈結束時,left 指向第一個滿足條件的最小容量
return left