LeetCode回溯问题 多问题总结

回溯合辑

LeetCode39

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

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

说明:

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

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

简单的回溯

基本就是回溯的模板,注意点就是返回的ArrayList不能作为参数放进方法中,因为每次回溯都会回到之前的状态。

直接上代码

class Solution {
    List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<Integer> temp =new ArrayList<Integer>();
        backstrace(candidates,target,temp,0);      
        return res;
    }
    public void backstrace(int[] candidates,int target,ArrayList<Integer> temp,int p){
        if(target==0){
            res.add(new ArrayList<>(temp));
            return;
        }
        if(target<0)return;
            for(int i=p;i<candidates.length;i++){                
                temp.add(candidates[i]);
                backstrace(candidates,target-candidates[i],temp,i);
                temp.remove(temp.size()-1);
            }
    }
}

LeetCode40

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

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

说明:

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

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

思路

主要的方法与上一题类似,这一题主要是如何除重的问题。除重的方法有两种:

  1. 使用Set天然的除重,但是代码结构会比较复杂
  2. 剪枝

注意到题目里说的是“每个数字在每个组合使用一次”,意味着数值相同但不是同一个的数字,可以在同一个组合中出现。即可以有[1,1,6]这种情况。不重复就需要按 顺序 搜索, 在搜索的过程中检测分支是否会出现重复结果 。注意:这里的顺序不仅仅指数组 candidates 有序,还指按照一定顺序搜索结果。

很容易想到的方案是:先对数组 升序 排序,重复的元素一定不是排好序以后相同的连续数组区域的第 1 个元素。也就是说,剪枝发生在:同一层数值相同的结点第 2、3 … 个结点,因为数值相同的第 1个结点已经搜索出了包含了这个数值的全部结果,同一层的其它结点,候选数的个数更少,搜索出的结果一定不会比第 1 个结点更多,并且是第 1 个结点的子集。

class Solution {
    List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        ArrayList<Integer> temp =new ArrayList<Integer>();
        Arrays.sort(candidates);
        backstrace(candidates,target,temp,0);      
        return res;
    }
    public void backstrace(int[] candidates,int target,ArrayList<Integer> temp,int p){
        if(target==0){
            res.add(new ArrayList<>(temp));
            return;
        }
        if(target<0)return;
            for(int i=p;i<candidates.length;i++){
                if(i>p&&candidates[i]==candidates[i-1])continue;//剪枝的位置
                temp.add(candidates[i]);
                backstrace(candidates,target-candidates[i],temp,i+1);
                temp.remove(temp.size()-1);
        }
        


    }
}

总体的代码和上一题差不多,关键是在剪枝的位置

 if(i>p&&candidates[i]==candidates[i-1])continue;

为什么这么写可以删除重复的。这里借用leetcode上看到的解释

这个避免重复当思想是在是太重要了。
这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即
                  1
                 / \
                2   2  这种情况不会发生 但是却允许了不同层级之间的重复即:
               /     \
              5       5
                例2
                  1
                 /
                2      这种情况确是允许的
               /
              2  
                
为何会有这种神奇的效果呢?
首先 cur-1 == cur 是用于判定当前元素是否和之前元素相同的语句。这个语句就能砍掉例1。
可是问题来了,如果把所有当前与之前一个元素相同的都砍掉,那么例二的情况也会消失。 
因为当第二个2出现的时候,他就和前一个2相同了。
                
那么如何保留例2呢?
那么就用cur > begin 来避免这种情况,你发现例1中的两个2是处在同一个层级上的,
例2的两个2是处在不同层级上的。
在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,
必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。
第一个出现的2的特点就是 cur == begin. 第二个出现的2 特点是cur > begin.

LeetCode46 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]

思路

基本的回溯,用一个标志数组用来除重。

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        boolean label[]=new boolean[nums.length];
        List<List<Integer>> res=new ArrayList<>();
        backstrace(res,new ArrayList<Integer>(),nums,label);
        return res;

    }
    public void backstrace(List<List<Integer>> res,ArrayList<Integer> temp,int nums[],boolean label[])
    {
        if(temp.size()==nums.length){
            res.add(new ArrayList(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(label[i]==true)continue;
            else{
                temp.add(nums[i]);
                label[i]=true;
                backstrace(res,temp,nums,label);
                label[i]=false;
                temp.remove(temp.size()-1);
            }
        }


    }
}

LeetCode47 全排列2

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

思路

与上一题不同的地方在于给的数组中有重复元素,

  1. 用set去重,只需增加一步用set去重
class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean label[]=new boolean[nums.length];
        List<List<Integer>> res=new ArrayList<>();
        backstrace(res,new ArrayList<Integer>(),nums,label);
        HashSet set=new HashSet(res);
      //新增的步骤
        res.clear();
        res.addAll(set);
        return res;

    }
}

2.剪枝,思想与40一样,允许下一个,不允许自身

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean label[]=new boolean[nums.length];
        List<List<Integer>> res=new ArrayList<>();
        Arrays.sort(nums);
        backstrace(res,new ArrayList<Integer>(),nums,label);
        return res;

    }
    public void backstrace(List<List<Integer>> res,ArrayList<Integer> temp,int nums[],boolean label[])
    {
        if(temp.size()==nums.length){
            res.add(new ArrayList(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(label[i]==true)continue;
            if(i>0&&label[i-1]==false&&nums[i]==nums[i-1])continue;//去除同一树层的重复
							  temp.add(nums[i]);
                label[i]=true;
                backstrace(res,temp,nums,label);
                label[i]=false;
                temp.remove(temp.size()-1);
            }
        }


    }

注意这里的

if(i>0&&label[i-1]==false&&nums[i]==nums[i-1])continue

label[i-1]==false的作用为判断是否在同一层,若在同一层则去除,为下一层则保留。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值