Partition Equal Subset Sum 分割等和子集
阿新 • • 發佈:2019-01-05
給定一個只包含正整數的非空陣列。是否可以將這個陣列分割成兩個子集,使得兩個子集的元素和相等。
注意:
- 每個陣列中的元素不會超過 100
- 陣列的大小不會超過 200
示例 1:
輸入: [1, 5, 11, 5] 輸出: true 解釋: 陣列可以分割成 [1, 5, 5] 和 [11].
示例 2:
輸入: [1, 2, 3, 5] 輸出: false 解釋: 陣列不能分割成兩個元素和相等的子集.
思路:
這道題其實可以變成是0 1 揹包問題,申請二維陣列dp[i][j](0<=i<=nums.size(),0<=j<=(sum/2),sum是陣列的和的一半),要劃分成兩半且和相等,即sum(left)=sum(right),那麼原陣列和必須是偶數,否則無法劃分。其中dp[i][j]表示從第一個元素到第i個元素是否存在能組成和為j的子集,如果可以為true,否則為false。
接下來我們來看遞推公式,看看dp[i][j]可以怎麼由子問題推導而來,先給出公式:
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
1:如果不考慮第i個元素,那麼情況等於前i-1個元素的情況即dp[i-1][j](前i-1個元素如果已經可以劃分子集左,那麼剩下的元素直接劃分到另外一邊即子集右即可)
2:如果考慮第i個元素,那麼情況等於前i-1個元素的子集和加上第i個元素的和可以組成和j,j-nums[i]表示前i-1個元素可以組成和為j-nums[i],那麼加上第i個元素nums[i],和即可j,可以組成子集。
所以是這兩種情況的或構成遞推公式,我一直覺得這道題和揹包問題的理解有點出入,因為01揹包問題是背或者不背,但一定所有的元素最後都會有結果(背或者不背),而這道題元素的背指的在左半邊子集,不背指的在右半邊子集,我們的目標是使得左半邊子集的和等於總和的一半。這樣思考才會和揹包問題對應上。
二維陣列版:
class Solution { public: bool canPartition(vector<int>& nums) { bool res = false; int sum = 0; int m = nums.size(); for (int i = 0; i < m; i++) { sum += nums[i]; } if ((sum & 1) == 1) { return false; } sum /= 2; bool **dp = new bool *[nums.size() + 1]; for (int i = 0; i <= m; i++) { dp[i] = new bool[sum + 1]; } for (int i = 0; i <= m; i++) { dp[i][0] = true; } for (int i = 1; i <= sum; i++) { dp[0][i] = false; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= sum; j++) { if (j >= nums[i - 1]) { dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } else { dp[i][j] = dp[i - 1][j]; } } } res = dp[m][sum]; for (int i = 0; i <= m; i++) { delete[] dp[i]; } delete[] dp; return res; } };
我們把空間複雜度優化到一維,觀察到對於內迴圈:
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= sum; j++) {
if (j >= nums[i - 1]) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
我們其實只需要上一層i-1的狀態,所以我們把二維陣列壓縮為一維,遞推公式如下:
dp[j] =dp[j] || dp[j - nums[i - 1]]; j>=nums[i-1]
這裡還需要注意一點,內層迴圈需要從大到小,避免被覆蓋。
參考程式碼:
class Solution {
public:
bool canPartition(vector<int>& nums) {
bool res = false;
int sum = 0;
int m = nums.size();
for (int i = 0; i < m; i++) {
sum += nums[i];
}
if ((sum & 1) == 1) {
return false;
}
sum /= 2;
bool *dp = new bool [sum + 1];
for (int i = 1; i <= sum; i++) {
dp[i] =false;
}
dp[0] = true;
for (int i = 0; i < m; i++) {
for (int j = sum; j > 0; j--) {
if (j >= nums[i]) {
dp[j] =dp[j] || dp[j - nums[i]];
}
}
}
res = dp[sum];
delete[] dp;
return res;
}
};