每周LeetCode算法题(五)
题目: 416. Partition Equal Subset Sum
Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.
Example 1:Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2:Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.
解法分析
题目的要求是,给定一个数组nums,判断里面的元素能不能分成和相同的两部分。
我的做法是,先求和,若数组的和本身就是奇数,那绝不可能被分成和相同的两部分,故返回false。若和为偶数,则进一步判断。
将和除以2,设为target,即现在我们的目标是从数组中抽出元素,看看能不能使它们的和恰为target,即全部元素的和的一半。
这里会用到动态规划的思想,以及空间换时间。
用一个长度为target+1的bool型数组reach来记录数组内抽出的任意数量的元素的和能达到多少,若能达到i,则reac[i]设为true。当整个判断过程结束,reach[target]上存的值就是最终结果。
具体一点,我们针对每个nums里的元素nums[i],尝试给它加上一个从0开始的数j,如果表示的由i前面某些元素相加的和j已经存在(即reach[j]值为true),那么再加上一个nums[i]一定会使reach[j + nums[i]]值成为true。
但这样有个问题是,在这以后进行的第j’ = j + nums[i]轮里,因为reach[j + nums[i]]为true,那么reach[j’ + nums[i]]也会为true,尽管实际上这个和可能并不存在。
所以我们还需要一个相同大小的数组pre,用来记录i的上一轮循环后reach的状态,不让reach在同一次循环中错误地将一次修改的值再传播到后面的多次中去。
说得可能不是很清楚,还是上代码吧。
C++代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 == 1) {
return false;
}
int target = sum / 2;
bool *reach = new bool[target + 1];
bool *pre = new bool[target + 1];
memset(reach, false, (target + 1) * sizeof(bool));
memset(pre, false, (target + 1) * sizeof(bool));
pre[0] = reach[0] = true;
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j + nums[i] <= target; j++) {
if (pre[j] == true) {
reach[j + nums[i]] = true;
}
}
for (int j = 0; j <= target; j++) {
pre[j] = reach[j];
}
}
return reach[target];
}
};
解法优化
为了避免上面的麻烦,对reach的遍历稍作了修改,j的循环改成从后往前,避免了多开一个数组存上一步的状态,效率稍稍提升了一点。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 == 1) {
return false;
}
int target = sum / 2;
bool *reach = new bool[target + 1];
memset(reach, false, (target + 1) * sizeof(bool));
reach[0] = true;
for (int i = 0; i < nums.size(); i++) {
for (int j = target - nums[i]; j >= 0; j--) {
reach[j + nums[i]] = reach[j + nums[i]] || reach[j];
}
}
return reach[target];
}
};