力扣刷题Day22

文章介绍了力扣上的组合总和问题,包括无数量限制但有总和限制的组合情况,以及处理重复元素的组合总和II问题。通过回溯算法解决,同时应用了剪枝策略以提高效率,如对数组排序和避免在同一树层上重复选取元素。还提到了分割回文串问题,利用回溯法寻找所有可能的切割方式。
摘要由CSDN通过智能技术生成

39. 组合总和

力扣链接:力扣

本题和77.组合 (opens new window)216.组合总和III (opens new window)的区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。

且题目中不用担心0怎么办,因为1 <= candidates[i] <= 200。

因此树形图可以抽象为:

这里递归的时候就没有层数限制,超过target就返回。

回溯三部曲:

函数的输出和输入:输出- viod;输入- 【全局变量 List<Integer> list - 符合条件的集合;List<List<Integer>>  result - 全部的结果】,int[] candidates, int index - 遍历的起始位置,int sum - 总和, int target

终止条件:当sum > target,那么就直接返回;如果sum == target,那么就将符合条件的集合放入结果集

单层循环逻辑: for循环(int i = index;i < candidates.length; i++), 将index位置的元素加入list,sum加上index位置的元素; backtraking(candidates, index, sum, target) -- 因为是重复添加,因此不需要+1; 回溯 - sum -= list.get(list.length-1), list.remove(list.length-1)。

具体代码实现:

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
            list = new ArrayList<>();
            result = new ArrayList<>();

            backtraking(candidates, target, 0, 0);

            return result;
    }

    List<Integer> list;
    List<List<Integer>> result;
    public void backtraking(int[] candidates, int target, int sum, int index){
        if(sum > target){
            return;
        }
        if(sum == target){
            result.add(new ArrayList<Integer>(list));
            return;
        }

        for(int i = index; i<candidates.length; i++){
            sum += candidates[i];
            list.add(candidates[i]);
            backtraking(candidates, target, sum, i);

            //回溯
            sum -= list.get(list.size() -1);
            list.remove(list.size() -1);
        }
    }
}

剪枝优化方案:

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

那么就需要先对数组进行排序。

具体代码实现:

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
            list = new ArrayList<>();
            result = new ArrayList<>();

            Arrays.sort(candidates);

            backtraking(candidates, target, 0, 0);

            return result;
    }

    List<Integer> list;
    List<List<Integer>> result;
    public void backtraking(int[] candidates, int target, int sum, int index){
        if(sum > target){
            return;
        }
        if(sum == target){
            result.add(new ArrayList<Integer>(list));
            return;
        }

        //剪枝优化
        for(int i = index; i<candidates.length && sum + candidates[i] <= target; i++){
            sum += candidates[i];
            list.add(candidates[i]);
            backtraking(candidates, target, sum, i);

            //回溯
            sum -= list.get(list.size() -1);
            list.remove(list.size() -1);
        }
    }
}

参考资料:代码随想录

40.组合总和II

题目链接:力扣

这道题不一样的就是,集合中有重复的元素,但是组合中是不能重复的,因此得进行去重。【注意组合中的元素是可以重复的如果存在重复的元素的话】

可以不去重,直接用hashset来储存组合,最后转换成list --- 但是这样的做法非常繁琐,且容易超时。

如何在搜索过程中去掉重复的元素呢?

所谓去重,其实就是使用过的元素不能重复选取。

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。

那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?

回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了  -  为了让相邻的元素都放在一起,这样会包含对这个元素所有组合的情况) 

 回溯三部曲:

1.输出和输入参数:输出 - void; 输入 - 【两个全局变量  List<Integer> list, List<List<Integer>> result】,int[] candidates, int target, int sum, int index;注意因为要去重,因此需要一个boolean数组 boolean[] used 「针对树层去重的操作」

2.终止条件:如果sum > target, 返回;如果sum == target,将list加入结果集

3.单层循环逻辑:

去重操作:

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

此时for循环里就应该做continue的操作。

for(int i= index; i<candidates.length; i++); 将index位置的元素放入list,将index位置的元素加入sum,used[i-1] == true;backtraking(candidates, target, sum, index+1); 回溯 - sum,list, used。

具体代码:

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        result = new ArrayList<>();
        list = new ArrayList<>();
        used = new boolean[candidates.length];

        //排序
        Arrays.sort(candidates);
        backtraking(candidates, target, 0, 0, used);

        return result;
    }

    List<List<Integer>> result;
    List<Integer> list;
    boolean[] used;
    public void backtraking(int[] candidates, int target, int sum, int index, boolean[] used){
        if(sum > target){
            return;
        }

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

        //剪枝操作
        for(int i = index; i < candidates.length && sum + candidates[i] <= target; i++){
            //去重
            if(i>0 && candidates[i-1] == candidates[i] && used[i-1] == false){
                continue;
            }

            sum += candidates[i];
            list.add(candidates[i]);
            used[i] = true;

            backtraking(candidates, target, sum, i+1, used);

            //回溯
            sum -= list.get(list.size()-1);
            list.remove(list.size()-1);
            used[i] = false; 
        }
    }
}

参考资料:代码随想录

131.分割回文串

题目链接:力扣 

本题这涉及到两个关键问题:

  1. 切割问题,有不同的切割方式
  2. 判断回文

切割问题类似组合问题

例如对于字符串abcdef:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

回归三部曲:

1.确定输出和输入参数:输出-void; 输入参数 - 【两个全局变量:List<List<String>> result; List<String> path -  单一结果】,backtraking(String s, int index-确定从哪个位置继续切割)

2.终止条件: 切割到字符串(index == s.length)最后,将path加入到result中,就返回;

3.单层搜索逻辑:

for(int i = index; i<s.length(); i++);找到切割的子串 - [index,i] - 判断这个子串是否为回文子串「定义一个新的函数isPalendrom(String s, int index, int i)」; 如果是回文的话,就用path进行收集,如果不是回文子串,则直接continue;

递归 - backtraking(s, i+1)

回溯 - path删除

具体代码实现:

class Solution {
    public List<List<String>> partition(String s) {
        result = new ArrayList<>();
        path = new ArrayList<>();

        backtraking(s, 0);

        return result;
    }

    List<List<String>> result;
    List<String> path;

    public void backtraking(String s, int index){
        //终止条件
        if(index == s.length()){
            result.add(new ArrayList<>(path));
            return;
        }

        for(int i=index; i<s.length(); i++){
            if(isPalindrome(s, index, i)){
                path.add(s.substring(index,i+1));
            }else{
                continue;//如果不是回文子串则继续向后切割
            }

            backtraking(s, i+1);//只有找到回文子串才会进行递归搜索

            //回溯
            path.remove(path.size()-1);
        }


    }

    public boolean isPalindrome(String s, int left, int right){
        for(int i = left, j = right; i<j; i++, j--){//两个变量进行for循环,可以参考下
            if(s.charAt(i) != s.charAt(j)){
                return false;
            }
        }

        return true;
    }
    
}

参考资料:代码随想录 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值