Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
Each number in C may only be used once in the combination.
Note:
- All numbers (including target) will be positive integers.
- The solution set must not contain duplicate combinations.
For example, given candidate set [10,
1, 2, 7, 6, 1, 5]
and target 8
,
A solution set is:
[ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
recursion中一道非常经典的问题 与Combination Sum的区别是candidates有可能有重复元素
以下是三种常见的解法
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);//1
List<List<Integer>> result = new ArrayList<>();
combine(candidates, target, 0, new ArrayList<>(), result);
return result;
}
private void combine(int[] candidates, int target, int i, List<Integer> cur, List<List<Integer>> result) {
if(target == 0) {
result.add(new ArrayList<>(cur));//2
return;
}
for (; i<candidates.length && candidates[i]<=target; i++) {
cur.add(candidates[i]);
combine(candidates, target-candidates[i], i+1, cur, result);
cur.remove(cur.size()-1);
while (i<candidates.length-1 && candidates[i+1]==candidates[i]) i++;//3
}
}
第二处标红的地方 如果写的是
result.add(cur);
那最终的结果都是相同的元素
[
[1,1,6],
[1,1,6],
[1,1,6],
[1,1,6]
]
因为cur是引用数据类型 在方法间传递的是引用 后面会被修改
在第三处标红的地方 就是防重
整个for循环的意思 是选择每次放入cur的最小元素
比如example中的输入,排序后是
1,1,2,5,6,7,10
第一次会首先放入1 第二次 因为1已经做过最小元素 所以不会再以1为最小元素
那会不会把[1,1,6]种种情况排除掉呢 答案是不会的 因为i=0时 会先放入1 target=8-1=7
然后第二个1还是小于target 所以会被放入
所以如果for循环的地方你这样写
for (; i<candidates.length && candidates[i]<=target; i++) {
if (i>0 && candidates[i]==candidates[i-1]) continue;
cur.add(candidates[i]);
combine(candidates, target-candidates[i], i+1, cur, result);
cur.remove(cur.size()-1);
}
就会把[1,1,6]给排除掉 因为第二个1加入不进来
为了方便解释 我们把2个1进行区分
1,1',2,5,6,7,10
最终结果中会出现1,2,5 那么我们排除了那种情况呢 排除的是1',2,5
对于这个题目 还有两种常见写法
1.使用一个boolean[] visited 存储哪些元素已经被访问过
2.选取一个元素之后 把此元素从原集合中删除 这样后面就不会出现重复访问的问题了
但是这两种方式都需要额外空间 也会让代码看上去不简洁