有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。