代码随想录第23天第24天 | 回溯 (二)
39. 组合总和
题目链接/文章讲解:组合总和
class Solution {
public:vector<vector<int>> res;vector<int> vec;void f(vector<int>& candidates, int target,int start){if(target==0){res.push_back(vec);return;}if (target < 0) {return; // 剪枝:目标值已为负,不可能满足}for(int i=start;i<candidates.size();i++){//这里也可来个剪枝if(target<candidates[i]) continue;target-=candidates[i];vec.push_back(candidates[i]);f(candidates,target,i);//即然可以重复则这里的开始位置就定为itarget+=candidates[i];vec.pop_back();}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {//这题和一般组合的区别在于可以重复使用里面的元素f(candidates,target,0);return res;}
};
40.组合总和II
这题的重难点在于保证所有方案不一致,如何去重?因为数组中有相同的元素,所以和其他题目去重的思路不太一致。
题目链接/文章讲解:组合总和II
举个例子【10 1 2 7 6 1 5】我们把它排序看一下【1 1 2 5 6 7 10】
之前说过任何的回溯问题都可以抽象为树结构,这题当然很好去抽象过去, 第一次取1,作为根节点的左子树,接着再取1,同理也可第一次取2,再取剩余的数组
则这样分析出来取数导致最终结果重复,其实是由于每一层有重复的元素,比如第一次取第一个1,第二三次取2和5,也可以第一次取第二个1,第二三次取2和5.
说明是每一次(抽象为树的层)取的数字相同就会导致重复。
那每一次层取的时候可以有一个used数组,在递归前把要取的数先看一下是否和used里的元素有相同的,若有则不取。
注意代码里取看是否有重复的是看used[i-1]是否为false,若为则需要跳过,因为说明前面的相同元素已经有过这个情况了,若再选这个则会出现重复现象。
class Solution {
public:vector<vector<int>> res;vector<int> vec;void f(vector<int>& candidates, int target,int start,vector<bool>& used){if(target==0){res.push_back(vec);return;}if (target < 0 || start >= candidates.size()) {return; // 添加边界条件检查}for(int i=start;i<candidates.size();i++){if(i>0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){//这里的candidate是为了确保前后两个数一样再判断是否需要跳过continue;}vec.push_back(candidates[i]);used[i]=true;f(candidates,target-candidates[i],i+1,used);vec.pop_back();used[i]=false;}}vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());vector<bool> used(candidates.size(), false);f(candidates,target,0,used);return res;}
};
131.分割回文串
本题属于分割问题
分割回文串
class Solution {
public:vector<vector<string>> res;vector<string> vec;//每次遍历的结果bool isPalindrome(const string& s, int start, int end) {for (int i = start, j = end; i < j; i++, j--) {if (s[i] != s[j]) {return false;}}return true;}void f(string s,int start){//结束条件if(start>=s.size()){res.push_back(vec);return;}//具体的遍历思路:for(int i=start;i<s.size();i++){if(isPalindrome(s, start, i)){string p= s.substr(start , i - start + 1);vec.push_back(p);}else{continue;}f(s,i+1);vec.pop_back();}}vector<vector<string>> partition(string s) {//先进行遍历再去判断是否为回文串,若为则放入结果数组中f(s,0);return res;}
};
93.复原IP地址
题目链接/文章讲解:复原IP地址
class Solution {
public:vector<string> res;void f(string& s,int start,int count){if(count==0){if (valid(s, start , s.size() - 1)) {res.push_back(s);}return;}for(int i=start;i<s.size();i++){if(valid(s, start, i)){s.insert(s.begin() + i + 1 , '.');f(s,i+2,count-1);//这里是+2,因为前面加了一个.s.erase(s.begin() + i + 1);//函数}else{break;}}}bool valid(const string& s, int start, int end) {if (start > end) {return false;}if (s[start] == '0' && start != end) { // 0开头的数字不合法return false;}int num = 0;for (int i = start; i <= end; i++) {if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法return false;}num = num * 10 + (s[i] - '0');if (num > 255) { // 如果大于255了不合法return false;}}return true;}vector<string> restoreIpAddresses(string s) {//有效IP地址由四个整数来组成,意思是这个字符串要分成四份,每一份要符合要求f(s,0,3);return res;}
};
78.子集
所谓去重,其实就是使用过的元素不能重复选取
子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。
题目链接/文章讲解:子集
class Solution {
public:vector<vector<int>> res;//res.push_back({});vector<int> v;void f(vector<int> nums,int start){res.push_back(v);for(int i=start;i<nums.size();i++){v.push_back(nums[i]);f(nums,i+1);v.pop_back();}}vector<vector<int>> subsets(vector<int>& nums) {//返回子集//首先是空集,再接着遍历f(nums,0);return res;}
};
90.子集II
大家之前做了 40.组合总和II 和 78.子集 ,本题就是这两道题目的结合。
题目链接/文章讲解:子集II
class Solution {
public:vector<vector<int>> res;vector<int> v;void f(vector<int> nums,int start,vector<bool> used){res.push_back(v);for(int i=start;i<nums.size();i++){if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){continue;}v.push_back(nums[i]);used[i] = true;f(nums,i+1,used);v.pop_back();used[i] = false;}}vector<vector<int>> subsetsWithDup(vector<int>& nums) {//返回子集但是元素中有重复元素sort(nums.begin(),nums.end());vector<bool> used(nums.size(), false);//初始化f(nums,0,used);return res;}
};
491.递增子序列
这题长得很像前面的,但实际上去重逻辑不同,有坑。
递增子序列
之前的去重是去重同一层不能有相同的元素则用used[i-1]=false,而这个去重是同一父节点下的元素不能相同。这就要每一个父节点就需要有一个去重的数组。
这里用unordered_set
来操作
由于其查找速度快且set本身就去重
class Solution {
public://同一父节点下的同层上使用过的元素就不能再使用了vector<vector<int>> res;vector<int> v;void f(vector<int>& nums,int start){if(v.size()>=2){res.push_back(v);//return;不能return否则只输出两个元素的情况}unordered_set<int> used; // 使用set来对本层元素进行去重for(int i=start;i<nums.size();i++){if(!v.empty() && nums[i] < v.back()|| used.find(nums[i]) != used.end()){//如果找到了,返回指向该元素的迭代器,若没找到则返回.end()continue;}used.insert(nums[i]);v.push_back(nums[i]);f(nums,i+1);v.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {//找出不同递增(非严格递增)子序列,且最少两个元素f(nums,0);return res;}
};