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