题目:
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
解法一:暴力枚举
我们在这种数组的子数组中求和的问题,很自然想到暴力枚举的方法。观察题目条件可知,题目的限制条件范围较小。我们不妨试试暴力枚举的方法。
combinationSum2类用来接收一个整数数组和一个目标和,返回所有可能的组合。在其中呢,我们创建一个result空数组用来接收数据。其次对于给定的一维数组进行排序。便于后面减少不必要的计算。combination数组用来构建当前的组合。然后利用backtrack_bruteforce类生成所有可能的组合。
backtrack_bruteforce类,首先进行了判断:若是剩余和为0的话,自然返回当前构建数组即可。
- 终止条件:如果
remain
等于0,说明当前组合的和正好等于目标和,因此将当前组合添加到结果中。 - 循环遍历:从
start
索引开始遍历候选数组。 - 去重处理:如果当前数字和前一个数字相同,并且前一个数字在当前分支中还没有被使用过(即
i > start
),则跳过当前数字。这是为了避免生成重复的组合。 - 选择:将当前数字添加到组合中。
- 递归调用:递归地调用
backtrack_bruteforce
函数,更新剩余和(remain - candidates[i]
)和起始索引(i + 1
),以便在下一次递归中使用下一个数字。 - 回溯:在递归调用返回后,撤销对当前数字的选择,以便尝试其他可能的组合。
通过这种方式,backtrack_bruteforce
函数能够生成所有可能的组合,这些组合的数字和正好等于目标和,并且没有重复的组合。
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
sort(candidates.begin(), candidates.end()); // 先排序以便后续去重
vector<int> combination;
backtrack_bruteforce(candidates, target, 0, combination, result);
return result;
}
private:
void backtrack_bruteforce(vector<int>& candidates, int remain, int start, vector<int>& combination, vector<vector<int>>& result) {
if (remain == 0) {
result.push_back(combination);
return;
}
for (int i = start; i < candidates.size(); ++i) {
// 去重处理:如果当前数字和前一个数字相同,并且前一个数字没有被使用,则跳过当前数字
if (i > start && candidates[i] == candidates[i - 1]) continue;
combination.push_back(candidates[i]);
backtrack_bruteforce(candidates, remain - candidates[i], i + 1, combination, result);
combination.pop_back();
}
}
};
在提交之后,发现暴力枚举还是太耗时间。这时候有人就要问了,老师,老师,这种解决办法还是太吃时间了,有没有快一点的呢?有点兄弟,有的!
解法二:剪枝搜索
核心思想还是暴力枚举,只不过在遍历途中对于一些已经不符合要求的进行去除。在这道题的具体实现中,我们已经排序了,所以若是左节点已经大于等于结果了,那么他的右节点就不用进行判断了,因为右节点一定比左节点大,所以肯定溢出了。
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> result;
sort(candidates.begin(), candidates.end()); // 先排序以便后续剪枝和去重
vector<int> combination;
backtrack_pruned(candidates, target, 0, combination, result);
return result;
}
private:
void backtrack_pruned(vector<int>& candidates, int remain, int start, vector<int>& combination, vector<vector<int>>& result) {
if (remain == 0) {
result.push_back(combination);
return;
}
for (int i = start; i < candidates.size(); ++i) {
// 去重处理:如果当前数字和前一个数字相同,并且前一个数字没有被使用,则跳过当前数字
if (i > start && candidates[i] == candidates[i - 1]) continue;
//if (candidates[i] > remain) break; // 剪枝:如果当前数字已经大于剩余和,则无需继续
combination.push_back(candidates[i]);
backtrack_pruned(candidates, remain - candidates[i], i + 1, combination, result); // 注意这里是 i + 1,每个数字只能用一次
combination.pop_back();
}
}
};
此时,我们再次提交。通过啦!!!