补录:day023-回溯法

40.组合II

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

思路:组合题目二,这个题目与组合一的区别在于,本题目中的candidates的每个元素只能使用一次,而且本题中的candidate数组中会有重复的元素。要对此进行去重

首先定义三个成员变量,一维和二维的数组和sum,sum代表累加的和。在回溯函数中如果当前的sum和等于目标值,将结果放到二维数组中并返回。然后继续往下看,通过for循环遍历目标数组(此时在for的循环条件里加入了剪枝的操作,当 当前的和加上当前对应的数组值小于等于目标值,才可以继续进入循环,否则的话就不进入循环,因为不满足条件进入循环也没必要了),如果i大于0(不能是第一层),并且当前对应的数组的值不能等于前一个值,如果相等,在遍历这第二个相同的值就没有意义了,因为前一个值所遍历的范围已经包含第二个值了,所以应该将第二个去重掉,(在这道题目中我定义了一个存储布尔类型的动态数组,并且它的大小与candidate的大小相同,来记录已经遍历过的元素,如果元素已经使用过,就设置为TRUE),在if条件中判断如果当前值与前一个相等并且前一个的used数组显示为FALSE。为什么一定是等于FALSE呢,等于TRUE不可以吗?如果等于TRUE的话,我们可能会遗漏一些正确的组合,如下图所示:

而我们实际要剪枝的是:也就是Carl哥说的树层和树枝去重(下图是树层,上图是树枝)

去重的条件大概就是这些,然后呢就是正常的回溯递归,注意还有一点的是我们依然采用了startIndex来更新递归后的搜索初始节点进行去重。

class Solution {
public:
vector<int> path;
vector<vector<int>> res;
int sum;
    void travelBacking(vector<int>& candidates, int target,int startIndex,vector<bool>& used){
        if(sum==target){
            res.push_back(path);
            return ;
        }
        for(int i=startIndex;i<candidates.size()&&sum+candidates[i]<=target;i++){
            if(i>0&&used[i-1]==false&&candidates[i]==candidates[i-1]){
                continue;
            }
            sum+=candidates[i];
            path.push_back(candidates[i]);
            used[i]=true;
            travelBacking(candidates,target,i+1,used);//每个数字在每个组合中只能使用一次
            used[i]=false;
            sum-=candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
            int startIndex=0;
            vector<bool> used(candidates.size(),false);
            sort(candidates.begin(),candidates.end());
            travelBacking(candidates,target,startIndex,used);
            return res;
    }
};

131.分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 
回文串。
返回 s 所有可能的分割方案。

 思路:分割字符串第一眼看似很难,但实际上与前面的组合题目很像的,需要增加的操作就是要定义一个判断回文串的函数,然后正常定义回溯函数,如果传入的startIndex要大于等于字符串长度,startIndex代表切割线,如果切割线等于字符串长度,代表切割到尾部了,将path存入result并返回,这是回溯三部曲的第二步,确认回溯终止条件,第一步是传入参数,第三步是确认单层递归逻辑,首先判断当前子串是否为回文串,如果是回文串进入循环,将其截取出来并存入字符串str中,注意截取时传入的参数是startIndex--字符串的起始位置和长度--i-startIndex+1,因为长度就是下标加1,如果非回文串的话,continue(continue 语句用于跳过当前循环的剩余部分,并立即开始下一次循环迭代);注意,有人可能不理解为什么开始值和结束值是startIndex和i呢? startIndex是起始位置,而i则是结束位置。

class Solution {
public:
vector<string> path;
vector<vector<string>> result;
    bool isPartition(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 travelBacking(string s,int startIndex){
        if(startIndex>=s.size()){
            result.push_back(path);
            return;
        }
        for(int i=startIndex;i<s.size();i++){
            if(isPartition(s,startIndex,i)){
                string str=s.substr(startIndex,i-startIndex+1);
                path.push_back(str);
            }else{
                continue;
            }
            travelBacking(s,i+1);
            path.pop_back();
        }
    }
    vector<vector<string>> partition(string s) {
            travelBacking(s, 0);
             return result;
    }
};

78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

 思路:此题相对于上面的题目来说算是简单的了,同样的回溯模版,差别在于在它是取所有的集合,要在回溯函数开始时将path加入到二维数组中,然后正常将该题目抽象成树形结构遍历。如图所示:(选自代码随想录图解)

class Solution {
public:
vector<int> path;
vector<vector<int>> res;
    void travelBacking(vector<int>& nums,int startIndex){
        res.push_back(path);
        if(startIndex>=nums.size()){
            return;
        }
        for(int i=startIndex;i<nums.size();i++){
            path.push_back(nums[i]);
            travelBacking(nums,i+1); 
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        travelBacking(nums,0);
        return res;
    }
};

90.子集II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 
子集
(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

 思路: 和上一道题很像,主要就是和组合二大体思路的去重,树层去重和树枝去重,通过一个used数组来记录是否使用过这个数字,当当前的值与前一个值相等时,并且前一个值为FALSE,代表是树层去重,无需遍历当前的树枝了,因为前面的树枝遍历已经包含了这个的树枝遍历,还需要注意的一点是,因为子集是找出所以的组合并记录,所以要在回溯函数开头将一维数组存到二维数组中。

class Solution {
public:
vector<int> path;
vector<vector<int>> res;
    void travelBacking(vector<int>& nums,int startIndex,vector<bool>& used){
        res.push_back(path);
        if(startIndex>=nums.size()){
             return;
        }
        for(int i=startIndex;i<nums.size();i++){
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false){
               continue;
            }
            path.push_back(nums[i]);
            used[i]=true;
            travelBacking(nums,i+1,used);
            used[i]=false;
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
            vector<bool> used(nums.size(),false);
            sort(nums.begin(),nums.end());
            travelBacking(nums,0,used);
            return res;
    }
};

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值