1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> ans = new ArrayList<>();
helper(ans, new ArrayList<>(), candidates, 0, target);
return ans;
}

private void helper(List<List<Integer>> ans, List<Integer> curr, int[] candidates, int pos, int remain) {
if (remain == 0) {
ans.add(new ArrayList<>(curr));
return;
}
for (int i = pos; i < candidates.length && candidates[i] <= remain; i++) {
if (i > pos && candidates[i] == candidates[i - 1]) {
continue;
}
curr.add(candidates[i]);
helper(ans, curr, candidates, i + 1, remain - candidates[i]);
curr.remove(curr.size() - 1);
}
}
}

递归函数是pos标记的位置及以后的元素中去凑remain, curr装的是目前已经凑的东西, 我们的目的是凑够remain再带上curr里的, 就能凑到target. for循环表示选择pos后的每一个位置作为当前的head然后去凑remain, 最后凑成的数字们前面加上curr的那些元素即可. 这里我们的操作是直接在curr里面加然后再及时取出来即可, 这也是backtrack的常规操作. 15行的if判断就是不能让同一个元素做两次head.

后来的想法总结:
我本来是想着每个位置的数字存在包括和不被包括两种状态. 然后我们遍历所有情况即可. 但是这种方式没办法避免duplicates或者很麻烦. 于是就有了这道题的解法. 我们从pos及以后的位置让每个元素都来做start然后去凑remain, 但是相同值的元素只能做一次. 大概就是这样.

时间复杂度: O(2^n) 因为让每个元素轮流做head的意思也就是不选该元素之前的所有元素. 原理和我们的想法是一样的.
空间复杂度: O(n)