组合总和 II
题目描述:
给定一个候选人编号的集合 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 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
思路分析:
相比于前面的组合总和题,本题最大的不同是本题的输入数组可能包含重复元素。那么我们需要考虑的问题是:在深度搜索的时候,相同的元素会重复搜索。造成这种重复的原因是相等元素在某轮中被多次选择。其次本题规定中的每个数组元素只能被选择一次。
为解决此问题,我们需要限制相等元素在每一轮中只被选择一次。由于数组是已排序的,因此相等元素都是相邻的。这意味着在某轮选择中,若当前元素与其左边元素相等,则说明它已经被选择过,因此直接跳过当前元素。
代码实现注解:
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
//定义一个返回结果的集合
List<List<Integer>> res = new ArrayList<>();
//定义一个存储节点值的集合
List<Integer> state = new ArrayList<>();
//升序排序
Arrays.sort(candidates);
//遍历开始点
int start = 0;
//深度搜索
dfs(state, target, res, start, candidates);
return res;
}
void dfs(List<Integer> state, int target, List<List<Integer>> res, int start, int[] candidates){
//搜索时小于的被剪枝,等于0说明当前存储节点刚好为零,返回存储节点
if(target == 0){
//将节点值存入返回集合
res.add(new ArrayList<>(state));
return;
}
for(int i = start; i < candidates.length; i++){
//剪枝操作,将叶子节点小于0的分支减掉,这是因为数组已排序,后边元素更大,子集和一定超过 target
if(target - candidates[i] < 0)
break;
//如果该元素与左边元素相等,说明该搜索分支重复,直接跳过
if(i > start && candidates[i] == candidates[i-1])
continue;
//更新target和start
state.add(candidates[i]);
//进行下一轮选择
dfs(state, target-candidates[i], res, i+1, candidates);
//回溯,移除最后一个元素
state.removeLast();
}
}
}