分割等和子集
分割等和子集
給你一個 只包含正整數 的 非空 陣列 nums 。請你判斷是否可以將這個陣列分割成兩個子集,使得兩個子集的元素和相等。
示例 1:
輸入:nums = [1,5,11,5]
輸出:true
解釋:陣列可以分割成 [1, 5, 5] 和 [11]
示例 2:
輸入:nums = [1,2,3,5]
輸出:false
解釋:陣列不能分割成兩個元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
思路
題意分析
題目是要找是否可以將這個陣列分割成兩個子集,使得兩個子集的元素和相等。
那麼只要找到集合裡能夠出現 sum / 2 的子集總和,就算是可以分割成兩個相同元素和子集了。
例如示例1:
輸入元素總和是22,要分成兩個元素和相等的子集,那這兩個子集各自之和肯定都是11,即sum / 2
解題方法
既然這題出現在dp章節了,那肯定就是用dp來做了
但是,如果第一次遇見,應該怎麼確定適用dp來解呢?可以先嚐試分析一下題目,看看能不能把題目往這邊靠
比如,這題中,每個構成子集的每個元素只能使用一次(關鍵點1)
這個描述可以聯想到01揹包問題,那就嘗試去抽象一下:
本題可以轉換為一個01揹包問題,目標是給定一個陣列,用陣列中的元素去填滿一個容量為 sum / 2 的揹包(如果能填滿的話,就找到了題目要求的子集)
還不夠,還要繼續確定什麼是物品(物品重量),什麼是價值
顯然,能夠作為物品的只有陣列裡的元素了,那麼物品的重量就是元素數值,物品的價值也是元素數值
現在就把01揹包問題套進來了,當前問題的條件如下:
- 揹包的體積是 sum / 2
- 要放入揹包的物品是陣列元素,其重量為元素數值,重量也為元素數值
- 揹包中每個元素不可重複使用
- 如果揹包正好裝滿,那麼說明找到了總和為 sum / 2 的子集(有一種就行,不用最優)
ok,現在可以五部曲去分析了
五步走
以一維01揹包問題為模板來解題,詳見
1、確定dp陣列含義
回顧一下分析一維陣列的揹包問題時,dp[j]的定義:在容量為j時,揹包能裝下的物品的最大價值
根據上面的分析,本題中物品重量是陣列元素數值,物品價值還是陣列元素數值
那上面的定義可以改為:揹包總容量是j,放入物品後,揹包的最大重量為dp[j]
即本題dp陣列的含義
舉個例子,假設揹包容量為target,那麼dp[target]就是揹包裝滿時的重量
當dp[target] = target,揹包就裝滿了,此時變找到了題解子集
這裡會出現一種情況:在所給的揹包容量下,沒有物品能裝滿
什麼意思?舉個例子,輸入是陣列 [1, 5, 11, 5],target是7
那麼,當dp[7]的時候,揹包只能放1、5,總價為6,沒放滿,顯然這不是我們需要的結果(不滿足dp陣列定義),不過好在我們是倒序遍歷,在之後的遍歷中,dp[6] = 6,滿足dp定義,並且我們也找到了一個滿足條件的子集[1,5]
2、確定遞推公式
其實可以直接套用一維dp陣列揹包問題的遞推公式:dp[j] = max(dp[j], dp[j - wight[i]] + value[i])
本題中,物品重量和價值都是陣列元素,所以可以改成:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
其含義是:
不放入物品時,容量為j的揹包最大的價值是dp[j];
放入物品時,容量為j - nums[i]的揹包所背的最大價值是dp[j - nums[i]];
3、確定如何初始化
還是參考一維揹包問題,當揹包容量為0,也就是dp[0]時,裡面不可能裝東西
因此,dp[0] = 0;
其他部分全部初始化為0(因為題目說了陣列元素全是正數),目的是為了防止倒序遍歷過程中,上一層的值與當前值疊加(詳見:一維dp陣列如何初始化)
又因為題目給了:每個陣列元素不會超過100,陣列本身的大小不會超過200,且元素總和不會超過20000
那麼建立dp陣列時只需要取一半的大小即可
vector<int> dp(10001, 0);
4、確定遍歷順序
都套一維dp陣列的揹包問題了,肯定是倒序遍歷
原因詳見:為什麼要倒序遍歷?
for(int i = 0; i < nums.size(); ++i){//遍歷物品
for(int j = target; j > 0; --j){//遍歷揹包容量
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
程式碼
根據上面的分析可寫出一下程式碼
步驟:
1、求陣列的總和,用於求揹包容量target
2、判斷當前總和sum是否為偶數,不為偶數就直接返回false
3、根據題目提示定義dp陣列,同時進行初始化
4、倒序遍歷dp陣列,先遍歷物品,再遍歷揹包容量,計算dp[j]
5、判斷dp[target] 是否等於 target
class Solution {
public:
bool canPartition(vector<int>& nums) {
//求揹包容量,也就是target
//先定義一個sum用於求陣列總和
int sum = 0;
for(auto num : nums) sum += num;
//如果sum值不能均分(不是偶數),那麼一定無法分為兩個和相等的子集,直接返回false
if(sum % 2 == 1) return false;
int target = sum / 2;
//定義dp陣列,同時初始化
vector<int> dp(10001, 0);
// //初始化dp陣列
// dp[0] = 0;
//遍歷dp陣列(注意是倒序遍歷)
for(int i = 0; i < nums.size(); ++i){//遍歷物品
// 如果當前揹包容量小於物品重量,換一個物品繼續遍歷容量
// 每一個元素一定是不可重複放入,所以從大到小遍歷
for(int j = target; j >= nums[i]; --j){//遍歷揹包容量
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
//判斷是否滿足題意條件
if(dp[target] == target) return true;
return false;
}
};