https://leetcode-cn.com/problems/combination-sum/submissions/
分析
这道题也是典型的回溯算法代表题,题目要求找到所有的解,每一个解都是由候选数组中的元素组成的,因此我们可以利用回溯算法,一次将一个元素加入到组合中,再判断组合是否是一个解。在回溯过程中还需要剪枝,剪掉不可能找到解的分支和会产生重复解的分支。
解法
最容易想到的方法,把候选数组中的元素一个个加入到组合中,然后计算组合中所有元素之和:如果大于目标数,则这个搜索分支不会找到解,剪枝掉;如果等于目标数,则这个组合作为一个解;如果小于目标数,则继续将元素加入到组合中,继续搜索。
注意,由于元素是可以无限次数重复使用的,所以dfs的每个节点都有n个分支(n即候选数组的大小)。
如何去重呢?不可能去判断一个解是否已被加入到答案中,因为数组的重复比较很麻烦。回想三数之和那道题,对组合去重的方法是:保证加入到答案中的组合里面的元素按升序排序。这个想法非常实用。我们可以在搜索到比组合中最末元素小的数时进行剪枝,从而保证加入到组合中的数字按升序排序。
回溯算法的代码如下:
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> permut;
sort(candidates.begin(), candidates.end());
dfs(result, candidates, target, permut);
return result;
}
private:
void dfs(vector<vector<int>>& result, const vector<int>& candidates, const int& target, vector<int>& permut) {
int sum = 0;
for (auto num : permut) sum += num;
if (sum == target) result.emplace_back(permut);
else if (sum > target) return;
for (auto num : candidates) {
int pre = permut.size() ? permut.back() : 0;
if (num < pre) continue;
permut.emplace_back(num);
dfs(result, candidates, target, permut);
permut.pop_back();
}
}
};
这个代码是可以优化的。
- 保证组合按升序排序,可以引入一个变量
start
,在遍历候选数组时,从start
开始遍历。 - 判断组合是否是一个解,这里用的是加法,从而导致每次都需要计算组合中元素之和。如果用减法的话,则逐步减小目标数的值,值为0时组合为一个解;值大于0时搜索继续;值小于0时这个分支搜索不到解,并且由于候选数组已排序,之后的数字也没必要参与搜索了,全部剪枝掉。
代码
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> permut;
sort(candidates.begin(), candidates.end());
dfs(result, candidates, target, permut, 0);
return result;
}
private:
void dfs(vector<vector<int>>& result, const vector<int>& candidates, const int& target, \
vector<int>& permut, int start) {
if (target == 0) result.emplace_back(permut);
for (int i = start; i < candidates.size() && target - candidates[i] >= 0; ++i) {
permut.emplace_back(candidates[i]);
dfs(result, candidates, target - candidates[i], permut, i);
permut.pop_back();
}
}
};