LeetCode416.分割等和子集
相似题目
难易程度
- middle
题目考察
- 回溯
- 动态规划
暴力解法
思路及算法:最容易想到的方法是用回溯法暴力搜索所有答案,使用二维数组res保存结果,path保存找到的一个数组。
//时间复杂度:O(N * 2^ N) 空间复杂度:O(N * M)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Solution{
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int> candidates, int target, int count, int startIndex, vector<bool>& used) {
// if (count > target) return fasle;
if (count == target) {
res.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && count + candidates[i] <= target; i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
count += candidates[i];
used[i] = true;
path.push_back(candidates[i]);
backtracking(candidates, target , count, i + 1, used);
used[i] = false;
count -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> findSameArray(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
res.clear();
path.clear();
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return res;
}
};
int main() {
vector<int> candidates = {1, 5, 2, 8};
int sum = 0;
for (int i = 0; i < candidates.size(); i++) {
sum += candidates[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2;
Solution solution;
vector<vector<int>> res = solution.findSameArray(candidates, target);
// int m = res.size(), n = res[0].size();
for (int i = 0; i < res.size(); i++) {
for (int j = 0; j < res[0].size(); j++) {
cout<< res[i][j]<< endl;
}
}
return 0;
}
优化解法
思路及算法:使用动态规划,使用01背包解法,背包体积为sum / 2,背包要放入的商品(集合里的元素)重量为元素的数值,价值为元素的数值,背包如何正好装满,说明找到了总和为sum / 2的子集。背包中每一个元素不可重复放入。
1.确定dp数组以及下标的含义
dp[i]表示 背包总容量是i,最⼤可以凑成i的⼦集总和为dp[i]
2.确定递推公式
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.dp数组如何初始化
⾮0下标的元素初始化为0就可以了,dp[0] = 0;
4.确定遍历顺序
// 开始 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]);
}
}
5.举例递归
//时间复杂度O(n ^ 2) 空间复杂度 O(n)
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
// dp[i]中的i表示背包内总和
// 题⽬中说:每个数组中的元素不会超过 100,数组的⼤⼩不会超过 200
// 总和不会⼤于20000,背包最⼤只需要其中⼀半,所以10001⼤⼩就可以了
vector<int> dp(10001, 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;
}
};
总结
通过分割相同子集的思考,能自然的想到使用回溯法求解所有解,使用动态规划的方法能减小时间复杂度,进而使用滚动数组来减小空间复杂度。