1.组合总和
题目链接/文章讲解: 代码随想录视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili
代码:(未剪枝版 )
class Solution {
private:vector<int> path;vector<vector<int>> result;void backtracking(vector<int>& candidates,int target,int startIndex){// 递归出口if(target < 0){return;}if(target == 0){result.push_back(path);return;}// 处理结点for(int i = startIndex;i < candidates.size();i++){target -= candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,i);path.pop_back();target += candidates[i];}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {if(candidates.size() == 0) return result;backtracking(candidates,target,0);return result;}
};
代码:(剪枝版)
class Solution {
private:vector<int> path;vector<vector<int>> result;void backtracking(vector<int>& candidates,int target,int startIndex){// 递归出口if(target < 0){return;}if(target == 0){result.push_back(path);return;}// 处理结点// 这里加了判断:如果下一轮的target已经小于0了,就没有必要递归了for(int i = startIndex;i < candidates.size() && target - candidates[i] >= 0;i++){target -= candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,i);path.pop_back();target += candidates[i];}}
public:vector<vector<int>> combinationSum(vector<int>& candidates, int target) {if(candidates.size() == 0) return result;sort(candidates.begin(), candidates.end()); // 涉及大小的剪枝必排序backtracking(candidates,target,0);return result;}
};
思路:学到了这种元素数量不限制的取的解法
其实startIndex变量还是要有,因为我们求的是组合,要数层去重;并且这是在同一个数组里取数。
而数量不限,体现在我在递归里传入的starIndex的值是i,而不是i+1了。之前说过for循环管控每一层数的宽度;递归则管控数的深度。这样更改后,就可以在取完一个数的情况后,继续取这个数。实现如下图所示的效果:
关于剪枝的操作。这种涉及到总和大小的取值时。剪枝就要将给定的数组排序! 而且要注意给定的数组元素里全是正数,只有正数才越加越大。
这道题,我直接用目标总和target取减去每一个结点的数值了,看最后是否被减为0了。
2.组合总和2
题目链接/文章讲解:代码随想录
视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili
代码:去重且剪枝过的
class Solution {
private:vector<int> path;vector<vector<int>> result;void backtracking(vector<int>& candidates,int target,int startIndex,vector<bool>& used){if(target < 0){return;}if(target == 0){result.push_back(path);return;}for(int i = startIndex;i < candidates.size() && target - candidates[i] >= 0;i++){// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过// used[i - 1] == false,说明同一树层candidates[i - 1]使用过// 要对同一树层使用过的元素进行跳过if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){continue;} // 数层去重used[i] = true;target -= candidates[i];path.push_back(candidates[i]);backtracking(candidates,target,i + 1,used);path.pop_back();target += candidates[i];used[i] = false;}}
public: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;}
};
思路:
这道题和上题的区别是:
每个元素只能使用一次——startIndex为i+1;
给定的元素有重复值,但是要去重后的结果:这里就是难点。首先去重是有两个维度的。
一个组合里可以有相同大小的不同元素,所以递归操作里不需要去重,即上图的同一个子树的数枝上不需要去重;不同组合不能相同,所以要在数层去重。
这里用一个数组used来记录,遇到的和前一个元素相同的情况(这里去重先去把给定数组排序)时,到底是和前一个相同元素是在同一个树枝里的,还是同一个树层里的。
在同一个树枝里:前一个元素已经加入到了path里,已经使用过了。
在同一个树层里:前一个元素没有加入到path里,没有被使用,这是独立的两个分支。
所以如果遇到这种树层里重复的情况,加个判断语句,直接continue(因为接下来的情况还有我们要收集的,所以只是跳过这一种情况,用continue)
3.分割回文串
代码随想录
视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibili
代码:
class Solution {
private:vector<string> path;vector<vector<string>> result;bool isPalindrome(string& s,int start,int end){while(start <= end){if(s[start] != s[end]){return false;}start++;end--;}return true;}void backtracking(string& s,int startIndex){// 递归出口if(startIndex >= s.size()){result.push_back(path);return;}// 遍历结点// 回文子串的范围是[startIndex,i]for(int i = startIndex;i < s.size();i++){// 在这里直接判断是否子串满足回文子串if(isPalindrome(s,startIndex,i)){string str = s.substr(startIndex,i - startIndex + 1);path.push_back(str);}else{continue;}backtracking(s,i + 1);path.pop_back();}}
public:vector<vector<string>> partition(string s) {if(s.size() == 0) return result;backtracking(s,0);return result;}
};
思路:
关于substr函数:
这道题很巧妙地将startIndex作为了子串的开始下标,子串的范围可以表示为[startIndex,i]。
同时,本题将判断条件放在了for循环的里面来判断,来决定是否要执行下面的递归回溯操作。判断用了一个额外的函数来判断回文子串。
细节问题其实就这些。