教你做回溯题(Leetcode组合总数和组合总数II)-第二期

定义什么的就不多说了,上期都有,还有这期涉及到上期的知识,最好结合上期看。

上期指路:递归回溯题目总结(第一期)

例题

组合总和

给定一个无重复元素的正整数数组和一个目标正整数,找出数组中所有可以使数字和为目标正整数的组合(数组中的一个数可以被重复使用)

①选择一个实例[2,3,5]与8画图

 这个图没有画完,但是基本清楚大致,流程。

②确定结束条件

定义一个sum,记录一次深入递归遍历的和,如果等于目标数,则结束,加入结果集。

//条件判断
if(sum==target){
    res.push_back(path);
    return;
}

③确定选择列表

我们明显可以发现每个子树的根节点下面一层的选择列表均为[2,3,5],因为可以重复选。也就是说这个start不会随着深度的增加而+1,而是始终保持不变。

for(int i=start;i<candidates.size();i++)

④剪枝去重

①中图我没有画完是有原因的,因为这题可以重复使用,所以如果不进行一些适当的剪枝处理,就会一直遍历无用的树枝。剪枝条件也简单。只要sum大于目标数,就直接跳过此次循环。

//减枝
if(sum>target){
    continue;
}

⑤⑥⑦选择、递归、撤销

由于可以重复选择,递归时i不变(即递归传过去的start不变)

//选择
path.push_back(candidates[i]);
//由于可以重复选择,这里递归就不需要i+1
dfs(candidates,path,res,target,sum+candidates[i],i);
//撤销
path.pop_back();

完整代码:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> path;
        vector<vector<int>> res;
        dfs(candidates,path,res,target,0,0);
        return res;
    }

    void dfs(vector<int>& candidates,vector<int>& path, vector<vector<int>>& res,int target,int sum,int start){
        //条件判断
        if(sum==target){
            res.push_back(path);
            return;
        }
        for(int i=start;i<candidates.size();i++){
            //减枝
            if(sum>target){
                continue;
            }
            //选择
            path.push_back(candidates[i]);
            //递归
            //由于可以重复选择,这里递归就不需要i+1
            dfs(candidates,path,res,target,sum+candidates[i],i);
            //撤销
            path.pop_back();
        }
    }
};

组合总和 II

给定一个数组(可以有重复数字)和一个目标数,找出数组中所有可以使数字和为目标数的组合。

每个数字只能使用一次

①选择实例[1,1,2]和3画图

  

 图未画全。

②确定结束条件与上面一题一样

③确定选择列表

由于这题是不能重复选取一个数的,所以选择列表左区间start应该是上一层选择的节点的下一个节点。

for(int i=start;i<candidates.size();i++)

④剪枝,很明显sum>target的剪枝条件必不可少,并且由于不能重复使用,所以同一层中不能使用相同的数,和上一期的第二题一样。当然这样的前提也是需要将数组排序,不然相同数不一定挨在一起

//排序,为剪枝做好准备
sort(candidates.begin(),candidates.end());
if(i>0 && candidates[i]==candidates[i-1])

但是我们又发现一个问题,虽然同一层中不能重复使用,但是由于数组中可能有相同数字导致不同层可以使用相同的数字(也就是说如果题目给的数组中有重复数字,最终结果有可能有重复数字的)而上面的条件将所有重复情况都否定了,所以不对。

 所以需要给每位数字设置一个是否使用的标志位

vector<bool> used(candidates.size(),false)

就是说我们需要剪枝的是同一层重复的,因为不在同一层,一个深度路线上的话,那么前一个会被设置为使用过,在同一层就是由于回溯对于当前其上一个没有使用过。

也就是说used[i-1]=true代表不在同一层,在深度路径,我们要剪枝的是同一层的,所以最终条件需要在基础上加上一个used[i-1]==false

//剪枝
if(i>0 && candidates[i]==candidates[i-1] && !used[i-1]){
    continue;
}

⑤⑥⑦选择、递归、撤销

由于一个数只能使用一次,所以递归时的i需要i+1(即传过去的start要是上次选择节点的下一个节点)

//选择
path.push_back(candidates[i]);
used[i]=true;
//递归
dfs(candidates,path,res,target,sum+candidates[i],i+1,used);
//撤销
path.pop_back();
used[i]=false;

完整代码:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> path;
        vector<vector<int>> res;
        vector<bool> used(candidates.size(),false);
        //排序,为剪枝做好准备
        sort(candidates.begin(),candidates.end());
        dfs(candidates,path,res,target,0,0,used);
        return res;
    }

    void dfs(vector<int>& candidates,vector<int>& path, vector<vector<int>>& res,int target,int sum,int start,vector<bool>& used){
        //条件判断
        if(sum==target){
            res.push_back(path);
            return;
        }
        for(int i=start;i<candidates.size();i++){
            if(!used[i]){
                //剪枝
                if(sum>target){
                    continue;
                }
                //剪枝
                if(i>0 && candidates[i]==candidates[i-1] && !used[i-1]){
                    continue;
                }
                //选择
                path.push_back(candidates[i]);
                used[i]=true;
                //递归
                dfs(candidates,path,res,target,sum+candidates[i],i+1,used);
                //撤销
                path.pop_back();
                used[i]=false;
            }
        }
    }    
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值