4.8 KiB
和Leetcode 473:火柴拼正方形和Leetcode 698:划分为k个相等的子集是
思路
这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
那么只要找到集合里能够出现 sum / 2 的集合,就算是可以分割成两个相同元素和子集了。
本来是我是想用回溯暴力搜索出所有答案的,各种剪枝,还是超时了,不想在调了,放弃回溯,直接上01背包吧。
如下的讲解中,我讲的重点是如何把01背包应用到此题,而不是讲01背包,如果对01背包本身还不理解的同学,需要额外学习一下基础知识,我后面也会在代码随想录里深度讲解背包问题。
背包问题
背包问题,大家都知道,就是书包,书包可以容纳的体积n, 然后有各种商品,每一种商品体积为m,价值为z,问如果把书包塞满(不一定能塞满),书包里的商品最大价值总和是多少。
背包问题有多种背包方式,常见的有:01背包、完全背包、多重背包、分组背包和混合背包等等。
要注意背包问题问题中商品是不是可以重复放入。
即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。
要明确本题中我们要使用的是01背包,因为元素我们只能用一次。
为了让大家对背包问题有一个整体的了解,可以看如下图:
回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。
那么来一一对应一下本题,看看背包问题如果来解决。
只有确定了如下四点,才能把背包问题,套到本题上来。
- 背包的体积为sum / 2
- 背包要放入的商品(集合里的元素)体积为 元素的数值,价值也为元素的数值
- 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素一定是不可重复放入。
定义里数组为dp[],dp[i] 表示 背包中放入体积为i的商品,最大价值为dp[i]。
套到本题,dp[i]表示 背包中总和是i,最大可以凑成总和为i的元素总和为dp[i]。
dp[i]一定是小于等于i的,因为背包不能装入超过自身体积的商品(这里理解为元素数值)。
如果dp[i] == i 说明,集合中的元素正好可以凑成总和i,理解这一点很重要。
C++代码如下(详细注释 )
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
// dp[i]中的i表示背包内总和
// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 那么背包内总和不会大于20000,所以定义一个20000大的数组。
vector<int> dp(20001, 0);
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2;
// 开始 01背包
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]);
}
}
// 集合中的元素正好可以凑成总和target
if (dp[target] == target) return true;
return false;
}
};
暴力
本来是想用回溯暴力搜索出所有答案的,各种剪枝,还是超时了,不想在调了,放弃回溯,直接上01背包吧。
回溯搜索超时的代码如下:
class Solution {
private:
int target;
bool backtracking(vector<int>& nums, int startIndex, int pathSum, vector<bool>& used) {
for (int i = startIndex; i < nums.size(); i++) {
if (pathSum > target) break; // 剪枝
if (target < nums[i]) break; // 剪枝
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { // 去重
continue;
}
pathSum += nums[i];
used[i] = true;
if (pathSum == target) return true;
if (backtracking(nums, i + 1, pathSum, used)) return true;
used[i] = false;
pathSum -= nums[i];
}
return false;
}
public:
bool canPartition(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (sum % 2 == 1) return false;
target = sum / 2;
cout << "sum:" << sum << " target:" << target << endl;
return backtracking(nums, 0, 0, used);
}
};