經典動態規劃 子集揹包問題

2022-07-04 02:18:09 字數 3362 閱讀 4336

416.分割等和子集

-----------

上篇文章 經典動態規劃:0-1 揹包問題 詳解了通用的 0-1 揹包問題,今天來看看揹包問題的思想能夠如何運用到其他演算法題目。

而且,不是經常有讀者問,怎麼將二維動態規劃壓縮成一維動態規劃嗎?這就是狀態壓縮,很容易的,本文也會提及這種技巧。

先看一下題目:

演算法的函式簽名如下:

// 輸入乙個集合,返回是否能夠分割成和相等的兩個子集

bool canpartition(vector& nums);

對於這個問題,看起來和揹包沒有任何關係,為什麼說它是揹包問題呢?

首先回憶一下揹包問題大致的描述是什麼:

給你乙個可裝載重量為w的揹包和n個物品,每個物品有重量和價值兩個屬性。其中第i個物品的重量為wt[i],價值為val[i],現在讓你用這個揹包裝物品,最多能裝的價值是多少?

那麼對於這個問題,我們可以先對集合求和,得出sum,把問題轉化為揹包問題:

給乙個可裝載重量為sum / 2的揹包和n個物品,每個物品的重量為nums[i]。現在讓你裝物品,是否存在一種裝法,能夠恰好將揹包裝滿

你看,這就是揹包問題的模型,甚至比我們之前的經典揹包問題還要簡單一些,下面我們就直接轉換成揹包問題,開始套前文講過的揹包問題框架即可。

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

第一步要明確兩點,「狀態」和「選擇」

這個前文 經典動態規劃:揹包問題 已經詳細解釋過了,狀態就是「揹包的容量」和「可選擇的物品」,選擇就是「裝進揹包」或者「不裝進揹包」。

第二步要明確dp陣列的定義

按照揹包問題的套路,可以給出如下定義:

dp[i][j] = x表示,對於前i個物品,當前揹包的容量為j時,若xtrue,則說明可以恰好將揹包裝滿,若xfalse,則說明不能恰好將揹包裝滿。

比如說,如果dp[4][9] = true,其含義為:對於容量為 9 的揹包,若只是用前 4 個物品,可以有一種方法把揹包恰好裝滿。

或者說對於本題,含義是對於給定的集合中,若只對前 4 個數字進行選擇,存在乙個子集的和可以恰好湊出 9。

根據這個定義,我們想求的最終答案就是dp[n][sum/2],base case 就是dp[..][0] = truedp[0][..] = false,因為揹包沒有空間的時候,就相當於裝滿了,而當沒有物品可選擇的時候,肯定沒辦法裝滿揹包。

第三步,根據「選擇」,思考狀態轉移的邏輯

回想剛才的dp陣列含義,可以根據「選擇」對dp[i][j]得到以下狀態轉移:

如果不把nums[i]算入子集,或者說你不把這第i個物品裝入揹包,那麼是否能夠恰好裝滿揹包,取決於上乙個狀態dp[i-1][j],繼承之前的結果。

如果把nums[i]算入子集,或者說你把這第i個物品裝入了揹包,那麼是否能夠恰好裝滿揹包,取決於狀態dp[i-1][j-nums[i-1]]

首先,由於i是從 1 開始的,而陣列索引是從 0 開始的,所以第i個物品的重量應該是nums[i-1],這一點不要搞混。

dp[i - 1][j-nums[i-1]]也很好理解:你如果裝了第i個物品,就要看揹包的剩餘重量j - nums[i-1]限制下是否能夠被恰好裝滿。

換句話說,如果j - nums[i-1]的重量可以被恰好裝滿,那麼只要把第i個物品裝進去,也可恰好裝滿j的重量;否則的話,重量j肯定是裝不滿的。

最後一步,把偽碼翻譯成**,處理一些邊界情況

以下是我的 c++ **,完全翻譯了之前的思路,並處理了一些邊界情況:

bool canpartition(vector& nums)  else }}

return dp[n][sum];

}

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

再進一步,是否可以優化這個**呢?注意到dp[i][j]都是通過上一行dp[i-1][..]轉移過來的,之前的資料都不會再使用了。

所以,我們可以進行狀態壓縮,將二維dp陣列壓縮為一維,節約空間複雜度:

bool canpartition(vector& nums)
這就是狀態壓縮,其實這段**和之前的解法思路完全相同,只在一行dp陣列上操作,i每進行一輪迭代,dp[j]其實就相當於dp[i-1][j],所以只需要一維陣列就夠用了。

唯一需要注意的是j應該從後往前反向遍歷,因為每個物品(或者說數字)只能用一次,以免之前的結果影響其他的結果

至此,子集切割的問題就完全解決了,時間複雜度 o(n*sum),空間複雜度 o(sum)。

_____________

經典動態規劃問題 0 1揹包問題

乙個揹包有一定的承重cap,有n件物品,每件都有自己的價值,記錄在陣列v中,也都有自己的重量,記錄在陣列w中,每件物品只能選擇要裝入揹包還是不裝入揹包,要求在不超過揹包承重的前提下,選出物品的總價值最大。每個物品只有1個,給定物品的重量w價值v及物品數n和承重cap。請返回最大總價值。狀態 dp i...

動態規劃經典問題 01揹包問題

分解為子問題,尋找子問題間的依賴關係。第二行 肯定不選擇物品i 第三行 進行判斷是否選擇物品i。偽 用動態規劃解決揹包問題 public class backbag 填表 for int i 1 i n i else return opt n capacity return opt public s...

動態規劃 從子集和問題到揹包問題

有乙個包含n個元素的集合s,每個元素ei都有乙個對應的權值wi。現在有乙個界限w,我們希望從s中選擇出部分元素,使得這些元素的權值之和在不超過w的情況下達到最大,這個便是子集合問題 事實上還有其他型別的子集和問題,本文暫不討論 舉個更具體一點的例子,某農民今年收成小番茄總重量為w萬斤,有n個採購商想...