组合综合三连
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
,进入下一层递归
- 此时的
可以通过剪枝,减少不必要的遍历情况:
- 首先给数组排序
- 在循环迭代中,如果
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);
}
}
}