算法刷题|39.组合总和、40.组合总和||、131.分割回文串

组合总和

题目:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

思路:首先抽象成多叉树,然后由于本题允许重复元素,所有递归下一层的时候起始下标就是从当前层的下标开始的(当递归的下一层不满足条件的时候,这个时候起始下标就会在外层循环+1)

组合总和

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0){
            return res;
        }
        List<Integer> path = new ArrayList<>();
        backtracking(candidates,target,0,0,path,res);
        return res;
    }
    private void backtracking(int[] candidates,int target,int sum,int startIndex,List<Integer> path,List<List<Integer>> res){
         // 防止一直向下遍历下去
        if(sum > target){
            return;
        }
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i<candidates.length;i++){
            path.add(candidates[i]);
            sum+=candidates[i];
            // 这里可以重复,所有这里startIndex参数就不能传i+1了
            // 这里不好理解就手动模拟一下过程就好理解了
            // 例如本题开始一直递归知道 path中的有 2,2,2 了,在加入下一个元素之前是判断了当前sum=6 是否>target
            // 如果大于就跳出循环,然后递归回到上一层,然后就弹出2,并且sum-2;最后path:2,2 sum = 4
            // 此时的i=0的,然后循环,i+1了,判断了sum+3 > target,发现是=7的不大于,就继续
            // 然后递归进去发现是终止条件,然后结果,然后递归回到上一层,然后往复循环
            backtracking(candidates,target,sum,i,path,res);
            path.remove(path.size()-1);
            sum-=candidates[i];
        }
    }
}

剪枝

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0){
            return res;
        }
        List<Integer> path = new ArrayList<>();
        // 剪枝的前提
        Arrays.sort(candidates);
        backtracking(candidates,target,0,0,path,res);
        return res;
    }
    private void backtracking(int[] candidates,int target,int sum,int startIndex,List<Integer> path,List<List<Integer>> res){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i<candidates.length;i++){
        	// 剪枝
            // 因为是有序的,如果当前元素加起来已经大于了target,说明后面的任何元素加起来都大于target,所以就没有必要继续往下走了
            if((sum+candidates[i]) > target){
                 break;
            }
            path.add(candidates[i]);
            sum+=candidates[i];
            backtracking(candidates,target,sum,i,path,res);
            path.remove(path.size()-1);
            sum-=candidates[i];
        }
    }
}

注意:判断那个sum大于target的条件一定要放在for循环里面,不能放在递归开头,因为for循环里面是break是终止循环,而return是返回到循环中,如果放在递归开头,实际上没有做到剪枝,因为还是会遍历那些sum>target的元素,只是说会返回而已。

组合总和||

题目:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

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

注意:解集不能包含重复的组合。

组合总和||
思路:本题和之前的最大的不同就是在于需要对组合去重,例如[1,7]和[7,1]是一样的需要去重;但是目标数组中又有重复的元素,所有我们需要对其排序使得相同的元素相邻,然后使用标记数组标记单个组合中是否使用过某个元素

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0){
            return res;
        }
        List<Integer> path = new ArrayList<>();
        // 标记是否使用过某个元素
        int[] used = new int[candidates.length];
        // 去除 重复组合的前提
        Arrays.sort(candidates);
        backtracking(candidates,target,0,0,path,res,used);
        return res;
    }
    private void backtracking(int[] candidates,int target,int sum,int startIndex,List<Integer> path,List<List<Integer>> res,int[] used){
        if(sum > target){
            return;
        }
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i<candidates.length;i++){
            // 去重
            // candidates[i] == candidates[i-1] 如果当前的元素和前一个元素一样,则跳过
            if(i > 0 && candidates[i] == candidates[i-1] && used[i-1] == 0){
                continue;
            }
            int val = candidates[i];
            path.add(val);
            sum+=val;
            used[i] = 1;
            backtracking(candidates,target,sum,i+1,path,res,used);
            path.remove(path.size() - 1);
            sum-=val;
            used[i] = 0;
        }
    }
}

至于去重的那个条件中为什么是used[i-1] = 0;是因为我们要判断前一个元素是否被用过,如果被用过,说明是在叶子节点收集的path是[1,1…],这个集合是符合条件的,因为这两个1是下标不同的1;这种情况我们就不能过滤掉;所有需要加一个used[i-1] = 0;如果等于0说明没有被使用过,说明当前数组[1,1,2]循环到了第二个1,这个时候第一个1的所有组合已经收集完了,然后如果在从第二个1开始收集的话,那么就和第一个1收集的结果重复了

分割回文串

题目:给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

分割回文串
思路:本题不能老想着切割,我可以转变思路就是两个指针指向的区间的字符串,下面代码中的startIndex和i的区间就是一个子串的区间

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> res = new ArrayList<>();
        if(s == ""){
            return res;
        }
        List<String> path = new ArrayList<>();
        backtracking(s,0,path,res);
        return res;
    }
    private void backtracking(String s,int startIndex,List<String> path,List<List<String>> res){
        if(startIndex >= s.length()){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i<s.length();i++){
            // 判断当前子串是不是回文
            if(isPalindrome(s,startIndex,i)){
                path.add(s.substring(startIndex,i+1));
                backtracking(s,i+1,path,res);
                path.remove(path.size()-1);
            }
        }
    }
    private boolean isPalindrome(String s,int left,int right){
        if(left < 0 || right > s.length()){
            return false;
        }
        while(left <= right){
            char c1 = s.charAt(left++);
            char c2 = s.charAt(right--);
            if(c1 != c2){
                return false;
            }
        }
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值