回溯

回溯

解决一个回溯问题,实际上就是一个决
策树的遍历过程。你只需要思考3个问题:

1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件

框架

result	=	[]
def	backtrack(路径,选择列表):
				if	满足结束条件:
					result.add(路径)
					return
				for	选择	in	选择列表:
					做选择
					backtrack(路径,	选择列表)
					撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」

for	选择	in	选择列表:
		#	做选择
		将该选择从选择列表移除
		路径.add(选择)
		backtrack(路径,	选择列表)
		#	撤销选择
		路径.remove(选择)
		将该选择再加入选择列表  //状态重置

举例说明

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:


所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
示例 2:

输入:candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

image.png

class Solution {
public:

      void backtrack(int l,vector<vector<int>>& res,vector<int> candidates,int target,vector<int> track) {
        if(0==target) {
            res.push_back(track);
            return;
        } else if(target<0) {
            return;
        }

        for(int i=l;i<candidates.size();i++) { //注意这里是L
            if(target-candidates[i]<0)
            break;  //剪枝
            track.push_back(candidates[i]); 
            target-=candidates[i];
            backtrack(i,res,candidates,target,track);  //排除重复选项

            track.pop_back(); // 状态重置
            target+=candidates[i];
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> track;
        int l=0;

        backtrack(l,res,candidates,target,track); 
        return res;
            
        
    }

  
};

这道题目的精华就是去除重复项,设置搜索起点begin

画出树形图

以输入:candidates = [2, 3, 6, 7], target = 7 为例:

img

说明:

以 target = 7 为 根结点 ,创建一个分支的时 做减法
每一个箭头表示:从父亲结点的数值减去边上的数值,得到孩子结点的数值。边的值就是题目中给出的 candidate 数组的每个元素的值;
减到 00 或者负数的时候停止,即:结点 00 和负数结点成为叶子结点;
所有从根结点到结点 00 的路径(只能从上往下,没有回路)就是题目要找的一个结果。
这棵树有 44 个叶子结点的值 00,对应的路径列表是 [[2, 2, 3], [2, 3, 2], [3, 2, 2], [7]],而示例中给出的输出只有 [[7], [2, 2, 3]]。即:题目中要求每一个符合要求的解是 不计算顺序 的。下面我们分析为什么会产生重复。

针对具体例子分析重复路径产生的原因(难点)
友情提示:这一部分我的描述是晦涩难懂的,建议大家先自己观察出现重复的原因,进而思考如何解决。

产生重复的原因是:在每一个结点,做减法,展开分支的时候,由于题目中说 每一个元素可以重复使用,我们考虑了所有的候选数,因此出现了重复的列表。

一种简单的去重方案是借助哈希表的天然去重的功能,但实际操作一下,就会发现并没有那么容易。

可不可以在搜索的时候就去重呢?答案是可以的。遇到这一类相同元素不计算顺序的问题,我们在搜索的时候就需要 按某种顺序搜索。具体的做法是:每一次搜索的时候设置 下一轮搜索的起点 begin,请看下图。

img

即:从每一层的第 2 个结点开始,都不能再搜索产生同一层结点已经使用过的 candidate 里的元素。

什么时候使用 used 数组,什么时候使用 begin 变量

什么时候使用 used 数组,什么时候使用 begin 变量
有些朋友可能会疑惑什么时候使用 used 数组,什么时候使用 begin 变量。这里为大家简单总结一下:

排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用 used 数组;
组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用 begin 变量。
注意:具体问题应该具体分析, 理解算法的设计思想 是至关重要的,请不要死记硬背。

另外一个题目216

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。
示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

答案

class Solution {
public:
    vector<vector<int>> res;
    vector<int> track;
    vector<vector<int>> combinationSum3(int k, int n) {
        int sum=0;
        backtrak(1,k,n,sum);
        return res;
       
    }
    void backtrak(int i,int k,int n,int sum) {
        if(sum==n&&track.size()==k) {
            res.push_back(track);
        }
        for(i;i<10;i++) {
            if(track.size()>k||sum>n){
                return;
            }
            auto a=find(track.begin(),track.end(),i);
            if(a!=track.end()) {
                continue;
            } else {
                sum+=i;
                track.push_back(i);
            }
       
        backtrak(i,k,n,sum);
        int b=track.back();
        track.pop_back();
        sum-=b;
        }
    }
    
};

另外一个题目

377. 组合总和 Ⅳ

难度中等202

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3]
target = 4

所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

超时了

class Solution {

    vector<vector<int>> used;
    vector<int> track;
    int res=0;
public:
    int combinationSum4(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        back_track(nums,target);
        return res;

    }

    void back_track(vector<int>& nums, int target) {
        if(target==0) {
             for(auto a:track) {
                cout<<a;
            }
            cout<<endl;
            auto result=find(used.begin(),used.end(),track);
            if(result !=used.end()) {
                return;
            } 
           
            res++;
            used.push_back(track);
            return;
        }
        for(int i=0;i<nums.size();i++) {
            if(target-nums[i]<0) return;
       
            track.push_back(nums[i]);
            target-=nums[i];
            

            back_track(nums,target);
            target+=nums[i];
            track.pop_back();

        }
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值