回溯算法概览

在leetCode 中涉及到回溯的题目中 有以下几个基本题目,

39.组合总和

40. 组合总和 II

46. 全排列

47. 全排列 II

77.组合

78. 子集

90. 子集 II

通过这些题目,我们可以容易的整理出回溯的基本模版

46.全排列

问题是这样的 :给定一个 没有重复数字的序列,返回其所有可能的全排列。

整体的回溯树如下图所示:
Alt

当前在红色节点上,我们选择的列表有1,3。由于2在后面,2将无法选择。那么就便变成了树的遍历

代码如下:

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> track = new LinkedList<>();
        backtrack(track,nums);
        return res;
    }

    public void backtrack(LinkedList<Integer> track,int[] nums){
        if(track.size() == nums.length){
            res.add(new ArrayList<>(track));
            return ;
        }
        for (int i=0;i<nums.length;i++){
          	//判断数据
            if(!track.contains(nums[i])){
                track.add(nums[i]);
                backtrack(track,nums);
              	//撤销操作
                track.removeLast();
            }
        }

    }
}

47.全排列2

这里在46的基础上,将参数从不重复元素的数组变成了一个可以包含重复元素的数组,最简单的做法是在46的基础上,将res从arrayList变成Set就可以了

这里有一个小tip:

对于重复的元素,一定要提前进行排序,这就避免了重复元素所处的位置不相邻而引起的相同数据被纳入。

39.组合总和

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

那么我们在46的基础上进行修改:

其中退出条件从trace的长度限制变成了总和为0

if(target ==0){
	res.add(new ArrayList<>(list));
	return;
}

别的就没有改动了

改进

我们可以添加限制条件来达到剪枝的目的,从而减少循环次数。

通过将数组进行排序之后,我们可以得出这个结构: 如果总和小于0之后,那么后续的元素就不用进行添加了,直接退出循环就可以了

代码如下

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public  List<List<Integer>> combinationSum(int[] candidates, int target) {
        LinkedList<Integer> track = new LinkedList<>();
        Arrays.sort(candidates);
        backtrack(candidates,track,target,0);
        return res;
    }

    public  void backtrack(int[] candidates, LinkedList<Integer> list,int target,int i){
        if(target ==0){
            res.add(new ArrayList<>(list));
            return;
        }
        if(target <0){
            return;
        }
        for (;i<candidates.length;i++){
            list.add(candidates[i]);
            backtrack(candidates,list,target-candidates[i],i);
            list.removeLast();
        }
    }
}

40 组合总和2

这一题在39的基础上,添加了一个限制:组合中的元素只能使用一次,也就是说 在每次进入backtrack的方法,每次就将i增加一个,同时将res变成set就可以完成

优化

我们使用Set的方法来解决重复的问题,我们如果需要使用List来解决问题的话,也就是回退的时候,添加一个判断,如果有当前元素与上一个元素相同的话,就跳过

代码

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        LinkedList<Integer> tracks = new LinkedList<>();
        backtrack(tracks,candidates,target,0);
        return res;
    }

    public  void backtrack(LinkedList<Integer> tracks,int[] candidates, int target,int i){
        if(target==0){
            res.add(new ArrayList<>(tracks));
            return;
        }
        if(i>=candidates.length){
            return;
        }

        for (;i<candidates.length;){
            if(target-candidates[i] >=0){
                tracks.add(candidates[i]);
                backtrack(tracks,candidates,target-candidates[i],++i);
                tracks.removeLast();
                while (i>0 && i<candidates.length && candidates[i] == candidates[i-1] ){
                    i++;
                }
            }else {
                break;
            }
        }
    }

}

总结

通过上述几题,已经可以总结出一个回溯算法的框架:

result = []
def backtrack(路径, 选择列表): 
	if 满足结束条件:
		result.add(路径) 
		return
	for 选择 in 选择列表: 
	做选择
	backtrack(路径, 选择列表) 
	撤销选择
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值