代码随想录训练营day27, 组合总和I/II, 分割回文串

这里先总结下对于回溯获得组合的startIndex设定:

如果是一个集合来求组合的话,就需要startIndex

如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex

组合总和

和III的区别就是,这个是可以重复从candidates中取数(当然他的数也是不会为0的),对数量k没有要求

版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回,那么可以在for循环的搜索范围上做做文章:

对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

(这里由于是从一个数组中取, 还是有很多细节的)

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        //需要排序才能保证后面的剪枝操作
        Arrays.sort(candidates);
        //这个start也是从0开始的,因为现在是从数组中取
        backtracking(candidates, target, 0, 0);
        return res;
    }
    public void backtracking(int[] candidates, int target, int sum, int start){
        //下面还是老规矩
        if(sum > target){
            return;
        }
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        //注意剪枝操作:
        for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
            //注意这里,由于他是从candidate数组中取数据,所以有所不同
            path.add(candidates[i]);
            sum += candidates[i];
            //这里不用i+1,因为他是允许重复的
            backtracking(candidates, target, sum, i);
            path.remove(path.size() - 1);
            sum -= candidates[i];
        }
    }
}

组合总和II

和上一道的区别就是这里不能使用同一个元素(但是如果两个元素相同的值还是可以的); 同时这道题的candidate元素是有重复的

先理解下这里的去重, 可以发现其实就是每一层上的元素要去重,而不是每个树枝上

小知识:Arrays.fill(arrayname, value); 用来填充数组

第一种解法需要设置一个boolean型used数组,判断是否用过元素

 

去重主要有两点:

candidates[i] == candidates[i - 1];

used[i - 1] == false;

这两个就说明是在同一树层使用过,需要跳过,说实话我没有完全理解, 涉及到很多use数据的内容

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();
    //定义used数据
    boolean[] used;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        used = new boolean[candidates.length];
        //加标志数组,用来辅助判断同层节点是否已经使用
        Arrays.fill(used, false);
        //一样要排序
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, 0);
        return res;
    }

    private void backtracking(int[] candidates, int target, int sum, int start){
        //前面的终止条件都是一样的,可以忽略大于,因为单层遍历时会有剪枝
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        
        //注意这里也有一个剪枝操作
        for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
            //开始判断去重
            if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){
                continue;
            }
            used[i] = true;
            sum += candidates[i];
            path.add(candidates[i]);
            //这里任然要i+1,因为是不能重复的
            backtracking(candidates, target, sum, i + 1);
            //回溯初始化
            used[i] = false;
            sum -= candidates[i];
            path.remove(path.size() - 1);
        }
    }
}

 第二种解法很巧妙也更简单, 原理类似于 Three Sum, 直接判断i > start && 前后相等就continue

这个方法最重要的作用也是,可以让同一层级,不出现相同的元素, 去重原理如图:

 直接拉代码:

class Solution {
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, 0);
        return res;

    }

    private void backtracking(int[] candidates, int target, int sum, int start){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }

        for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
            //开始剪枝
            if(i > start && candidates[i] == candidates[i - 1]){
                continue;
            }
            path.add(candidates[i]);
            sum += candidates[i];
            backtracking(candidates, target, sum, i + 1);
            path.remove(path.size() - 1);
            sum -= candidates[i];
        }
    }
}

分割回文串

和组合问题差不多

依然使用递归三部曲:

  1. 终止条件就是, 切割线切到了字符串最后面,说明找到了一种切割方法(startIndex ≥ s.length())
  2. 单层递归:需要判断是不是回文,如果是回文,就用substring获取在s中的字串,add
  3. 所以可以发现还需要一个判断回文的方法:双指针一前一后判读

注意点:

  1. 注意终止条件是分割到最后一个了
  2. 注意字符串不能直接分割,需要利用substring, 参数是(start, i+1)
  3. 判断回文双指针定义好
  4. class Solution {
        List<String> path = new ArrayList<>();
        List<List<String>> res = new ArrayList<>();
        public List<List<String>> partition(String s) {
            backtracking(s, 0);
            return res;
        }
    
        //递归方法
        private void backtracking(String s, int start){
            //终止条件:说明找到了分割条件
            if(start >= s.length()){
                res.add(new ArrayList<>(path));
                return;
            }
            //开始回溯, 然后这里开始判读
            for(int i = start; i < s.length(); i++){
                if(isPalindrome(s, start, i)){
                    //由于字符串不能直接分割,所以要调用substring方法
                    String str = s.substring(start, i + 1);
                    path.add(str);
                } else {
                    continue;
                }
                backtracking(s, i + 1);
                path.remove(path.size() - 1);
            }
        }
        //isPalindrome method, 注意这里的start和end会变化的
        private boolean isPalindrome(String s, int start, int end){
            while(start < end){
                if(s.charAt(start) != s.charAt(end)){
                    return false;
                }
                start++;
                end--;
            }
            //注意:这里必须在条件内判断!=,才可以保证每个数都被遍历到了
            return true;
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值