题目
<中等> 组合总和 II
来源:LeetCode.
给定一个候选人编号的集合 candidates 和一个目标数 target ,
找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
- 1 < = c a n d i d a t e s . l e n g t h < = 100 1 <= candidates.length <= 100 1<=candidates.length<=100
- 1 < = c a n d i d a t e s [ i ] < = 50 1 <= candidates[i] <= 50 1<=candidates[i]<=50
- 1 < = t a r g e t < = 30 1 <= target <= 30 1<=target<=30
接下来看一下解题思路:
参考 回溯算法 + 剪枝
思路:回溯:
与 组合之和 的差别在于:
- 组合之和 :
candidates
中的数字可以无限制重复被选取; - 本题:
candidates
中的每个数字在每个组合中只能使用一次。
如何去掉重复的元素
数组 candidates
有序,也是 回溯
过程中实现「剪枝」的前提。
将数组先排序的思路来自于这个问题:去掉一个数组中重复的元素。很容易想到的方案是:先对数组 升序
排序,重复的元素一定不是排好序以后相同的连续数组区域的第
1
1
1 个元素。
也就是说,剪枝发生在:同一层数值相同的结点第
2
2
2、
3
3
3 … 个结点,因为数值相同的第
1
1
1 个结点已经搜索出了包含了这个数值的全部结果,同一层的其它结点,候选数的个数更少,搜索出的结果一定不会比第
1
1
1 个结点更多,并且是第
1
1
1 个结点的子集。
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> combine = new ArrayList<>();
if (candidates.length <= 0) {
return result;
}
// 排序
Arrays.sort(candidates);
dfs(candidates, target, result, combine, 0);
return result;
}
void dfs(int[] candidates, int target, List<List<Integer>> result, List<Integer> combine, int begin) {
if (target == 0) {
result.add(new ArrayList<>(combine));
return;
}
for (int i = begin; i < candidates.length; ++i) {
// 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
if (target - candidates[i] < 0) {
break;
}
// 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue
if (i > begin && candidates[i] == candidates[i - 1]) {
continue;
}
combine.add(candidates[i]);
// 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
dfs(candidates, target - candidates[i], result, combine, i + 1);
combine.remove(combine.size() - 1);
}
}
}
避免重复的思想:
可以让同一层级,不出现相同的元素,但是父子结构可以出现相同的结构。同一层级相同的元素,表示同一个元素多次使用,父子结构相同的元素,表示同等的不同元素。
避免重复的是这条语句:
i > begin && candidates[i] == candidates[i - 1]
- 在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,
必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。
第一个出现的2的特点就是 i == begin. 第二个出现的 2 特点是 i> begin. - 首先 c a n d i d a t e s [ i ] = = c a n d i d a t e s [ i − 1 ] candidates[i] == candidates[i - 1] candidates[i]==candidates[i−1] 是用于判定当前元素是否和之前元素相同的语句。