代码随想录day20:回溯分割

 39. 组合总和

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backTracking(candidates, target , 0, 0);
        return res;
    }

    private void backTracking(int[] candidates,int target, int sum, int Index){
        if(sum > target) return;
        if(sum == target){
            res.add(new ArrayList(path));
            return;
        }
        for(int i = Index; i < candidates.length; i++){
            path.add(candidates[i]);
            sum += candidates[i];
            backTracking(candidates, target, sum, i);
            sum -= candidates[i];
            path.remove(path.size() - 1);
        }
    }
}

自己A了,有几点java获取长度的知识做个总结。

Java 里的 length 和 length() 和 size() 的区别与理解

首先来区分一下这三种:

length – 数组 (int[]double[]String[]) – 用来获得数组长度

length() – 和字符串相关的对象 (StringStringBuilder, etc) – 获得数组的长度

size() – Collection 对象 (ArrayListSet, etc) – 获得 Collection 对象的大小

40. 组合总和 II

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;
    int sum = 0;

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        used = new boolean[candidates.length];
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return res;
    }

    public void backTracking(int[] candidates, int target,int startIndex){
        if(sum == target){
            res.add(new ArrayList(path));
            return;
        }
        for(int i = startIndex; i < candidates.length; i++){
            if(sum + candidates[i] > target){
                break;
            }
            if(i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]){
                continue;
            }
            used[i] = true;
            sum += candidates[i];
            path.add(candidates[i]);
            backTracking(candidates, target, i +1);
            sum -= candidates[i];
            path.remove(path.size() - 1);
            used[i] = false;
        }

    }
}

卡哥 的思路和代码都写出来了,时间问题优化版的还没看,明天 看一下优化版liweiwei的 题解,同是看一下 path的回溯,卡哥代码中

LinkedList<Integer> path = new LinkedList<>();
path.removeLast();

 的区别,Deque中也是removeLast()

liweiwei的优化版本

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        Arrays.sort(candidates);
        dfs(candidates, target, res, path, 0);
        return res;
    }
    public void dfs(int[] candidates, int target, List<List<Integer>> res, List<Integer> path, int index) {
        if(target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }
        Set<Integer> set = new HashSet<>();
        for(int i = index; i < candidates.length; i++) {
            //小于0,则该数字不可能取到,直接越过
            if(target - candidates[i] < 0) continue;
            //如果在set集合中已经有,也越过。同层剪枝 考虑[1,2,2,2,3] target=5的例子
            if(!set.add(candidates[i])) continue;
            path.add(candidates[i]);
            dfs(candidates, target - candidates[i], res, path, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

 将维护是否使用的布尔数组改成  if cur > begin,

解释语句: if cur > begin and candidates[cur-1] == candidates[cur] 是如何避免重复的。



这个避免重复当思想是在是太重要了。
这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即
                  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.

131. 分割回文串

    List<List<String>> res = new ArrayList<>();
    List<String> path = new ArrayList<>();
    String s;

    public List<List<String>> partition(String s)
    {
        this.s = s; //将题目输入的字符串变为全局变量
        dfs(0);
        return res;
    }

    public void dfs(int startIndex)
    {
        //当子串的起始点超出范围时,说明分割结束。记录此次分割方案
        if(startIndex == s.length())
        {
            res.add(new ArrayList<>(path));
            return;
        }

        //递归更新起始点,for循环延伸子串
        //起始点的更新优先于子串的延伸
        for (int i = startIndex; i < s.length(); i++)
        {
            if (isPalindrome(startIndex, i))
            {
                //记录一个回文子串。从startIndex(包括)提取子字符串,直到i + 1(不包括)
                path.add(s.substring(startIndex, i + 1));
                //以下一个字符为新的起始点
                dfs(i +1);
                //恢复现场,接下来以startIndex为起始,延长一下子串再试试
                path.remove(path.size() - 1);
            }
        }
    }

    /**
     * @param left 子串的起始索引
     * @param right 子串的末尾索引
     */
    public boolean isPalindrome(int left, int right)
    {
        while (left < right)
        {
            if (s.charAt(left) != s.charAt(right))
                return false;
            left++;
            right--;
        }

        return true;
    }

这个比卡哥的好懂,不明白卡哥Java版本代码中sb为什么没回溯,明天探究一下再补充。

应为 

  • 不同实例:由于每次递归创建新的 StringBuilder,每个实例的状态是独立的。
  • 无需回溯:不需要手动撤销对 sb 的修改,因为每次递归都是基于一个新的 StringBuilder

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值