題目網址:https://leetcode.cn/problems/partition-equal-subset-sum/
題意:給一只含正整數的非空 array nums, 返回是否能將 nums 切割成兩個 subset, 使得兩個 subset 的元素和相等。

Solution 1:
想法:利用 DP, 因為此題可看作是 0-1 背包問題, 從前 i 個數中取出一些數, 使這些數之和為 target, 其中 dp[i][j] 代表前 i 個數是否能取出一些數, 使得這些數和為 j, 可得以下公式:
- 不選擇
nums[i]:dp[i][j] = dp[i - 1][j]
- 選擇
nums[i]:dp[i][j] = dp[i - 1][j - nums[i]], 其中 j >= nums[i]
class Solution { public: bool canPartition(vector<int>& nums) { const int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % 2 == 1) { return false; }
const int target = sum / 2; const int maxNum = *max_element(nums.begin(), nums.end()); if (maxNum > target) { return false; }
const int n = nums.size(); vector<vector<bool>> dp(n, vector<bool>(target + 1, false));
for (int i = 0; i < n; ++i) { dp[i][0] = true; }
for (int i = 1; i < n; ++i) { for (int j = 1; j <= target; ++j) { if (j >= nums[i]) { dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; } else { dp[i][j] = dp[i - 1][j]; } } }
return dp[n - 1][target]; } };
|
- time:$O(n \cdot sum)$ ➔ for loop, 因為 target = $\dfrac{sum}{2}$
- space:$O(n \cdot sum)$ ➔
dp
Solution 2:
想法:改進 Solution 1, 因為 dp[i][j] 只會用到上一列的元素, 故不需要開到 $O(n \cdot sum)$ 空間, 只需記住上一列的狀態即可, 這樣只需要 $O(2 \cdot sum)$ space
class Solution { public: bool canPartition(vector<int>& nums) { const int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % 2 == 1) { return false; }
const int target = sum / 2; const int maxNum = *max_element(nums.begin(), nums.end()); if (maxNum > target) { return false; }
const int n = nums.size(); vector<bool> dp(target + 1, false); dp[0] = true;
for (int i = 1; i < n; ++i) { vector<bool> nextRow = dp; for (int j = 1; j <= target; ++j) { if (j >= nums[i]) { nextRow[j] = dp[j] || dp[j - nums[i]]; } else { nextRow[j] = dp[j]; } } dp = move(nextRow); }
return dp[target]; } };
|
- time:$O(n \cdot sum)$ ➔ for loop, 因為 target = $\dfrac{sum}{2}$
- space:$O(sum)$ ➔
dp 和 nextRow
Solution 3:
想法:改進 Solution 2, 將空間從 2 * sum 降到 sum, 其中 j 的 for loop 必須從後面來, 這樣才不會在同一次 loop 中用到剛賦值的
e.g. nums = [2, 2, 3, 5], target = 6, 沒解
- 順著來:
i = 1
j = 2 時, dp[i][2] = true
j = 4 時, dp[i][4] = dp[i][2] = true
j = 6 時, dp[i][6] = dp[i][4] = true, 不符答案
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
| T |
F |
T |
F |
T |
F |
T |
- 倒著來:
i = 1
j = 6 時, dp[i][6] = dp[i][4] = false
j = 4 時, dp[i][4] = dp[i][2] = false
j = 2 時, dp[i][2] = dp[i][0] = true
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
| T |
F |
T |
F |
F |
F |
F |
最終輸出為:下表, 得到答案為 false
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
| T |
F |
T |
T(i=3) |
T(i=2) |
T(i=4) |
F |
class Solution { public: bool canPartition(vector<int>& nums) { const int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % 2 == 1) { return false; }
const int target = sum / 2; const int maxNum = *max_element(nums.begin(), nums.end());
if (maxNum > target) { return false; }
const int n = nums.size(); vector<bool> dp(target + 1, false); dp[0] = true;
for (int i = 1; i < n; ++i) { for (int j = target; j >= 0; --j) { if (j >= nums[i]) { dp[j] = dp[j] || dp[j - nums[i]]; } } }
return dp[target]; } };
|
- time:$O(n \cdot sum)$ ➔ for loop, 因為 target = $\dfrac{sum}{2}$
- space:$O(sum)$ ➔
dp