【LeetCode和牛客网刷题】组合综合(#39,#40,#216)

组合综合三连

39.组合综合Ⅰ

力扣

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

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

说明:

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

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

关键词:

可以重复使用同一个数,解集中没有重复的组合,无重复数组

算法:

  • 准备一个dfs递归函数,dfs(int[] candidates, int index, int target, List<List<Integer>> res, List<Integer> combine)

  • 其中:candidates为题目提供的数组,index是当前遍历到的位置, target是不断更新的目标数字,res是要返回的结果列表,combine是在dfs过程中不断生成的组合列表

  • 在dfs函数中,如果当前的target已经变为0,我们就可以终止当前的dfs,说明一个可以构成target的组合已经实现了

  • 否则,从接下来的数中寻找合适的组合,从当前位置开始向后遍历,将遇到的数加入combine列表中,然后递归进入下一次dfs(因为可以多次选择同一个位置上的数,向下一层的递归中,index参数可以保持不变)

  • 递归完成后会涉及到回溯,即返回递归栈的上一层,需要将刚加入combine的数弹出(这样才能保证回到当前dfs步骤的上一个状态)

    比如:

    • 此时的index为0,candidates[index] == 2;
    • 已经不断重复使用了当前元素:
    • 2→2→2→ (此时target已经为1)
    • 进入下一次递归发现,target-2<0 ,return
    • 回到上一层递归,将结尾的2去掉:2→2→
    • 通过下一次迭代,将 candidates[index+1]放入combine ,进入下一层递归

可以通过剪枝,减少不必要的遍历情况:

  1. 首先给数组排序
  2. 在循环迭代中,如果target-candidates[i]<0 ,说明当前的值已经比target大了,因为已经排好序,所以当前值后面的数无论如何也不会凑成target ,至此可以终止当此的dfs

代码:

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<List<Integer>> res = new ArrayList<>();
        ArrayList<Integer> combine = new ArrayList<>();
				//排序是剪枝的前提
        Arrays.sort(candidates);
        dfs(candidates, 0, target, res, combine);
        return res;

    }

    public void dfs(int[] candidates, int index, int target, List<List<Integer>> res, List<Integer> combine){
        if(target == 0){
            res.add(new ArrayList<>(combine));
            return;
        }

        for(int i = index; i<candidates.length; i++){
            if(target-candidates[i]<0){
                return;
            }
            combine.add(candidates[i]);
            dfs(candidates, i, target-candidates[i], res, combine);
            combine.remove(combine.size()-1);
        }
    }
}

40.组合综合Ⅱ

力扣

给定一个数组 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,7】【1,7】中的两个1不来自同一个位置,也算作重复的集合

算法:

  • 在上题大剪枝的前提下完成一次小剪枝
  • 所谓小剪枝:因为事先将数组排好了序,相同的数字肯定是挨着的,对于每一个挨着的数字,其下一层的dfs肯定都是相同的,所以我们要将每一个相同数字下面的dfs结果归并到同一个数下,即剪去了这些具有相同数字的枝
  • 实现过程是:在for循环迭代中,如果遇到candidates[i]==candidates[i-1] ,就continue ,不进行任何操作

代码:

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
				//排序是剪枝的前提
        Arrays.sort(candidates);
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> combine = new ArrayList<>();
        dfs(candidates, 0, target, res, combine);
        return res;
    }

    public void dfs(int[] candidates, int index, int target, List<List<Integer>> res, List<Integer> combine){
        if(target == 0){
            res.add(new ArrayList<>(combine));
            return;
        }

        //从当前的index开始向后遍历:(以当前剩下的每一个数为root,向下递归)
        for(int i = index; i<candidates.length; i++){
            //大剪枝,如果target已经比当前的元素小,之后的元素肯定也不可能构成target,直接剪掉当前枝
            if(target-candidates[i]<0){
                break;
            }

            //小剪枝,如果当前元素和上一个元素相同,跳过,这种相同的元素的计算结果可以并入这些相同元素中的第一个
            if(i>index && candidates[i] == candidates[i-1]){
                continue;
            }

            //经过以上剪枝,当前的数字可以放心的加入组合中
            combine.add(candidates[i]);

            //探索下一个数
            dfs(candidates, i+1, target-candidates[i],res,combine);

            //回溯,还原当前数添加之前的状态
            combine.remove(combine.size()-1);
    
        }
        
    }
}

216. 组合综合Ⅲ

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

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

输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

关键字:

这次没有数组,1-9的整数,每个组合不存在重复数字,解集不包含重复组合

此题可以继续套用以上模板,只需要在一些小细节方面做一些更改即可#1,#2,#3,另外因为不涉及到数组,也就不存在数组中具有重复数字的情况,也不涉及到排序剪枝的预处理

所以在for循环迭代中,可以天然避免解集中出现重复组合

为了不让组合中出现重复数字,下一次dfs中,只要让当前数字+1即可

代码:

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {
        //target = n, k个数
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> combine = new ArrayList<>();
        dfs(n, k, 1, combine, res);
        return res;
        
    }

    public void dfs(int target, int k, int curNum, List<Integer> combine, List<List<Integer>> res){
				//#1 为了保证解集中的组合,都具有相同的元素数,需要加上一个条件
        if(target == 0 && combine.size()==k){ 
            res.add(new ArrayList<>(combine));
        }
				//#2 如果当前的组合中元素数量已经超过k,说明这个组合不可行,终止
        if(combine.size() == k) return;

				//能够进行到这里,说明当前组合没有完成,通过迭代,继续向下进行dfs
        for(int i = curNum; i<=9; i++){
				//判断当前元素是否可以加入combine组合,如果不行,剪枝
            if(target-i<0){
                break;
            }
				//能够进行到这里说明当前元素可以放心加入
            combine.add(i);
				//#3 保证组合中不存在重复元素,i必须+1
            dfs(target-i, k, i+1, combine, res);
				//回溯的必经过程
            combine.remove(combine.size()-1);
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值