39 组合总和
题目描述: 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明: 所有数字(包括 target)都是正整数;解集不能包含重复的组合。
思路: 回溯 + 剪枝。由于candidates数组中不存在重复元素,则不需判断解集中是否包含重复的组合。
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, int sum, int pos) {
if(pos == candidates.size()) {
if(sum == target) {
ans.push_back(tmp);
// mp[tmp] = 1;
}
return ;
}
for(int i = 0; i <= (target - sum) / candidates[pos]; i++) { // i表示取在pos位置的数的次数,0次为不取, 最多取 (target - sum) / candidates[pos]次。
if(i == 0) // 不取candidates[pos]这个数,不需要回溯,直接考虑下一个数
dfs(candidates, target, ans, tmp, sum, pos + 1);
else {
for(int j = 0; j < i; j++) { // 取i次candidates[pos]这个数,需要对压入和加和重复i次。
tmp.push_back(candidates[pos]);
sum += candidates[pos];
}
dfs(candidates, target, ans, tmp, sum, pos + 1);
// 回溯 回到没有对candidates[pos]这个数进行操作的状态。
for(int j = 0; j < i; j++) {
tmp.pop_back();
sum -= candidates[pos];
}
}
}
return ;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// sort(candidates.begin(), candidates.end());
vector<vector<int>> ans;
vector<int> tmp;
// map<vector<int>, int> mp;
int sum = 0;
dfs(candidates, target, ans, tmp, sum, 0);
return ans;
}
};
40 组合总和2
题目描述: 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数;解集不能包含重复的组合。
思路1: 和39题的区别在于数组candidates中可能存在重复元素。并且candidates中的每个数字在每个组合中只能使用一次。即该题与39题的差别为如何避免重复。 在本题中,用map避免重复,并进行多次剪枝(见注释)。
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, map<vector<int>, int>& mp, int sum, int pos) {
if(sum > target) // 剪枝
return ;
if(sum == target) { // 剪枝
if(mp[tmp] == 0) {
ans.push_back(tmp);
mp[tmp] = 1;
}
return ;
}
if(pos == candidates.size()) {
if(sum == target && mp[tmp] == 0) {
ans.push_back(tmp);
mp[tmp] = 1;
}
return ;
}
dfs(candidates, target, ans, tmp, mp, sum, pos + 1);
sum += candidates[pos];
tmp.push_back(candidates[pos]);
dfs(candidates, target, ans, tmp, mp, sum, pos + 1);
sum -= candidates[pos];
tmp.pop_back();
return ;
}
static bool cmp(int a, int b) {
return a > b;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end(), cmp); // 排序去重 从大到小排序 方便后续剪枝
vector<vector<int>> ans;
vector<int> tmp;
map<vector<int>, int> mp;
int sum = 0;
int start = 0;
for(int i = 0; i < candidates.size(); i++) { // 剪枝 选择起始点
if(candidates[i] <= target) {
start = i;
break;
}
}
dfs(candidates, target, ans, tmp, mp, sum, start);
return ans;
}
};
思路2: 用map去重时,由于重复数目太多,搜索空间比较大,耗时高。我们可以提前遍历candidates数组,统计其中每个元素的出现次数。按照每个元素出现0次->最高次数回溯枚举。这样就不会产生重复答案。
class node {
public:
int num;
int cnt;
};
class Solution {
public:
void dfs(int target, vector<vector<int>>& ans, vector<int>& tmp, vector<node>& store, int sum, int pos) {
if(sum > target)
return ;
if(sum == target) {
ans.push_back(tmp);
return ;
}
if(pos == store.size())
return ;
for(int i = 0; i <= store[pos].cnt; i++) {
if(i != 0) {
sum += store[pos].num;
tmp.push_back(store[pos].num);
}
dfs(target, ans, tmp, store, sum, pos + 1);
}
for(int i = 1; i <= store[pos].cnt; i++) {
sum -= store[pos].num;
tmp.pop_back();
}
return ;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> ans;
vector<int> tmp;
// 统计candidates数组中每个数字出现的次数。统计前需要对candidates数组排序。
vector<node> store;
int pre_sum = candidates[0];
int cnt = 1;
for(int i = 1; i < candidates.size(); i++) {
if(candidates[i] == pre_sum)
cnt++;
else {
store.push_back(node{pre_sum, cnt});
pre_sum = candidates[i];
cnt = 1;
}
}
if(cnt > 0)
store.push_back(node{pre_sum, cnt});
// for(int i = 0; i < store.size(); i++) {
// cout<<store[i].num<< " "<< store[i].cnt<<endl;
// }
int sum = 0;
dfs(target, ans, tmp, store, sum, 0);
return ans;
}
};
思路3: 如何去掉一个数组中重复的元素,除了使用哈希表以外,我们还可以先对数组升序排序,重复的元素一定不是排好序以后的第 1 个元素和相同元素的第 1 个元素。这个方法最重要的作用是,可以让同一层级,不出现相同的元素。但是却允许了不同层级之间的重复。
在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。第一个出现的2的特点就是 cur == begin。 第二个出现的2 特点是cur > begin。
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& tmp, int sum, int pos) {
if(sum > target)
return ;
if(sum == target) {
ans.push_back(tmp);
return ;
}
if(pos == candidates.size())
return ;
for(int i = pos; i < candidates.size(); i++) {
if(i > pos && candidates[i] == candidates[i - 1])
continue;
sum += candidates[i];
tmp.push_back(candidates[i]);
dfs(candidates, target, ans, tmp, sum, i + 1);
sum -= candidates[i];
tmp.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> ans;
vector<int> tmp;
int sum = 0;
dfs(candidates, target, ans, tmp, sum, 0);
return ans;
}
};
216 组合总和3
题目描述: 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明: 所有数字都是正整数。解集不能包含重复的组合。
思路: dfs回溯。1-9九层dfs,每层确定当前数字取或者不取。最终判断取到的数字个数是否为k个。
class Solution {
public:
// 和为n k个数
void dfs(int k, int n, vector<vector<int>>& ans, vector<int>& tmp, int sum, int num, int cnt) {
if(num == 10) {
if(cnt == k && sum == n) { // 有组成个数的限制
ans.push_back(tmp);
}
return ;
}
dfs(k, n, ans, tmp, sum, num + 1, cnt);
sum += num;
tmp.push_back(num);
cnt++;
dfs(k, n, ans, tmp, sum, num + 1, cnt);
sum -= num;
tmp.pop_back();
cnt--;
return ;
}
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> ans;
vector<int> tmp;
int sum = 0;
dfs(k, n, ans, tmp, sum, 1, 0);
return ans;
}
};
377 组合总和4
题目描述: 给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
思路1: DFS回溯会超时。
思路2: 由于不需要得出具体的组合表示,因此DP可以是求解本题的方法。首先求出数组中所有元素的最大公约数,用target除以最大公约数,如果模为0,则存在所求组合方案,否则返回0。
class Solution {
public:
// 动态规划
int combinationSum4(vector<int>& nums, int target) {
if(nums.size() == 0)
return 0;
int gcd = nums[0];
for(int i = 0; i < nums.size(); i++) {
gcd = __gcd(gcd, nums[i]);
}
if(target % gcd != 0)
return 0;
vector<unsigned long long> dp(target + 1, 0);
dp[0] = 1;
for(int i = 0; i < dp.size(); i++) {
for(int j = 0; j < nums.size(); j++) {
if(i - nums[j] >= 0)
dp[i] += dp[i - nums[j]];
}
}
// for(int i = 1; i < dp.size(); i++) {
// cout<<dp[i]<<endl;
// }
return (int)dp[target];
}
};