[動態規劃系列] —— 揹包DP之01揹包

01揹包問題

有n個物品,1個容量v的揹包,第i個物品體積是volume[i],價值是value[i],問將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大,每一個物品只能使用1次。

考慮中間狀態,有i個物品,有j個容量,該狀態的最高價值爲status[i][j]。該狀態能夠由其上一個狀態轉移得到,對於第i個物品,咱們能夠將其丟棄或放入揹包,取二者最大值。python

丟棄:status[i][j] = status[i-1][j]數組

放入:status[i][j] = value[i] + status[i-1][j-volume[i]]code

這裏的(i,j)狀態,是第i個物品已經作出選擇的結果,他須要上一個狀態即選擇第i-1個物品後的結果轉移而來。j - volume[i]能夠理解爲,選擇物品i後的容量是j,那麼未選擇以前爲j - volume[i]。leetcode

def solution(n, v, volume, value):
    status = [[0]*(v+1) for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, v+1):
            if j - volume[i-1] >= 0:
                status[i][j] = max(status[i-1][j], value[i-1] + status[i-1][j-volume[i-1]])
            else: status[i][j] = status[i-1][j]
    return status[n][v]

對於矩陣status,其狀態只在i-1和i兩行之間轉移,咱們能夠使用滾動數組也就是隻用兩行來不斷地更新,維護上一步和當前步的狀態,以達到下降空間複雜度的目的。get

這裏使用狀態壓縮,只用一行來記錄狀態,將二維DP降到一維DP。將上面的代碼稍做改動,能夠獲得下面的錯誤代碼,這裏只是單純的把i狀態刪掉了。雖然是錯誤的代碼,但咱們須要它來輔助理解。it

def solution(n, v, volume, value):
    status = [0]*(v+1)
    for i in range(1, n+1):
        for j in range(1, v+1):
            if j - volume[i-1] >= 0:
                status[j] = max(status[j], value[i-1] + status[j-volume[i-1]])
            else: status[j] = status[j]
    return status[v]

該段代碼的問題在於,對於status[j]的計算須要依賴於status[j-volume[i]]的結果,在二維中,status[j]和status[j-volume[i]]是分別處於不一樣行的,也就是i與i-1兩行,可是在一維中,它們都在同一行。io

而且j值是向右增加的,也就是說在計算status[j]時,status[j-volume[i]]的值早已被更新爲第i行的狀態了,而不是上一步的狀態。因此咱們須要讓j值向左增加。獲得以下代碼。ast

def solution(n, v, volume, value):
    status = [0]*(v+1)
    for i in range(1, n+1):
        for j in range(v, 0, -1):
            if j - volume[i-1] >= 0:
                status[j] = max(status[j], value[i-1] + status[j-volume[i-1]])
            else: status[j] = status[j]
    return status[v]

這段代碼已是正確的了,可是還不夠完美,觀察後不難發現status[j] = status[j]這一步是徹底沒有必要存在的,所以咱們能夠控制j的最小值,保證j - volume[i-1] >= 0成立。獲得最終代碼以下。class

def solution(n, v, volume, value):
    status = [0]*(v+1)
    for i in range(1, n+1):
        for j in range(v, volume[i-1]-1, -1):
            status[j] = max(status[j], value[i-1] + status[j-volume[i-1]])
    return status[v]

之因此該問題被稱做01揹包,其緣由在於對於每同樣物品只能使用一次。在咱們遇到的題目中,每每是01揹包問題的變體,咱們須要學會如何將題目轉換爲經典的01揹包問題。co

相關題目:分割等和子集一和零最後一塊石頭的重量 II

相關文章
相關標籤/搜索