算法笔记
之前已经做过了两种组合总和的问题,这一题与39题类似,但两题有两处不同点,所以要在原有的基础上去改进。
题目描述
本题与39题题干大致相同,但有两点需要进行区分,第一点就是给定的候选数组candidates不再是一个无重复数组,另外同一个数字不再被无限制重复被选取,考虑到以上两个问题,就可以从这两方面入手去解题。
解题过程
我们可以先从问题二入手,即无限制重复被选取,39题的题目要求数组可以重复使用所以我们不用同之前一样去在参数中对i+1,直接i就可以了,本题正好反过来在参数中对i + 1就可以解决问题二。
其次就是比较难处理的问题一,去重的步骤,若只解决问题一,输出结果里出现了重复的数组,得到的结果应该如下:
我们可以将回溯问题看作是一个树的问题,那重复的元素就有两种情况,一种是同一个树层上的重复,另一种是同一个树枝的重复。从刚才输出结果与题干中可以发现,在每条路径即一维数组里面存在重复内容,而没有出现重复的路径,看看下图,就可以认为同一树层中的元素是我们去重的目标。
对于去重的过程,我们需要引入一个used数组来记录同一个树枝上的元素是否被记录过,记录过为true,未被记录则为false,为了方面理解,我使用了代码随想录中的一张图片来解释过程,我觉得还是更容易理解一些。从图中看如果上下层选取了相同的元素,会有可能出现树层重复的情况,但光有这种情况还不够,我们还要看之前的used数组是否被选过。比如第二层第二个,第一个已经选取了一次,第二个再选取的时候它之前的used数组为false,再取就会导致同层重复。同理第三层第一个,它的上一层已经选过1,它再次选择就会发现之前的used数组为true,这就说明这是树枝重复。
所以我们可以认为在candidates[i] == candidates[i - 1]的情况下:
·如果used[i - 1] == true,说明同一树枝使用过candidates[i - 1]
·如果used[i - 1] == false,说明同一树层使用过candidates[i - 1]三部曲:
1.选择参数:首先需要一个记录每条路径的一维数组path以及返回最后结果的二维数组result,然后考虑的部分就是加和,所以我们要额外定义一个sum来表示总和,我们代码中将这些都设置为全局变量,另外我们的回溯函数中也需要startIndex去记录节点,再最后加上我们的uesd数组来解决重复问题。
2.终止条件:如果我们的sum大于target就说明这个组合没什么用了,直接return,如果sum==target就让result数组去记录这个组合,同时也要return,与39题一致。
3.循环递归回溯:首先给定一个for循环,进行去重的判断,如果i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false,那就重复了没有必要进行下去了。之后用path数组收集我们的每一个点,使用sum加上每一个点来与target进行判断,将used数组设置为true,随后回溯,回溯完将一切都恢复原状,used重新设置为false,撤销节点,与模板中的过程时是差不多的,这里要记得sum还要将之前加的部分退出去,不然撤销的节点就会发生变化,至此任务就完成了。
ps:在给的函数里面也要定义一下uesd数组并把它初始化即可。
代码部分
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
int sum;
void backtracking(vector<int>& candidates, int target, int startIndex, vector<bool>& used) {
if(sum > target) {
return;
}
if(sum == target) {
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
path.push_back(candidates[i]);
sum += candidates[i];
used[i] = true;
backtracking(candidates, target, i + 1, used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, used);
return result;
}
};
剪枝优化
对于本题的剪枝优化我采用了与39题同样的方式,即在终止条件中,如果sum大于target就跳出循环,但是在for循环中递归仍然进行,同时sum是每一层candidates的加和,所以可以判断 sum + candidates[i] <= targe就是我们所需要的条件,至此操作完成,上面的代码是已经优化过的,这里就不再放代码了。
结语
本题主要的难点就在于去重这一部分,我对于这一部分内容的理解花了很多时间,我个人觉得是有些抽象的,需要结合图示去好好理解,多回顾一下,争取可以在之后完全掌握。