【Leetcode & 剑指offer刷题】回溯算法-排列组合II 39 & 40
一、第39题. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都是独一无二的。
1 <= target <= 500
Example1
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
Example2
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
二、第40题. 组合总和II
题目链接
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
Example1
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
Example2
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
三、两题比较
- 第一题的candidate数组里面的数字是不重复的,而第二题可重复
- 第一题可以重复拿取数字;而第二题限制重复拿取
- 两题都返回不重复的组合
难点在于第一点:candidate有重复数字,但最后返回的是不重复的组合
此难点本文主要参考了Carl大佬的文章
组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。
从上面可以看出,“我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重”。
在实现过程中,我们用一个used数组记录同一树枝上的元素是否使用过。
四、代码
第39题
class Solution {
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0);
return res;
}
private void backtracking(int[] candidates, int target, int sum, int startIndex){
//需要判断sum和target的关系(小于target的时候不需要管)
if (sum == target){
res.add(new ArrayList<>(path));
return;
}
if (sum > target){
return;
}
//剪枝部分类似于上一篇的题
for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){
sum += candidates[i];
path.addLast(candidates[i]);
backtracking(candidates, target, sum, i);
sum -= candidates[i];
path.removeLast();
}
}
}
第40题
class Solution {
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// boolean数组初始化默认是false
boolean[] used = new boolean[candidates.length];
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0, used);
return res;
}
//相比上一题,参数增加used数组
private void backtracking(int[] candidates, int target, int sum, int startIndex, boolean[] used){
if (sum == target){
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){
//注意判断条件,当前数字如果和前一个相等,且前一个是没被用过的,说明同一树层用过,所以要跳过
if (i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false){
continue;
}
sum += candidates[i];
path.addLast(candidates[i]);
used[i] = true;
//由于不能重复选取,所以参数startIndex=i+1
backtracking(candidates, target, sum, i + 1, used);
sum -= candidates[i];
path.removeLast();
used[i] = false;
}
}
}