题目描述:
给定一个数组 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]
]
解题思路:
题目的要求求出所有和为target的组合,并且所有元素只能使用一次,因此可以通过递归加回溯的方法来解决。
使用dfs(pos,rest)表示递归的函数,作用是找到何为target的值。其中pos表示我们当前递归到数组candidate中的pos的个数,rest表示我们还需要选择和为rest的数放入列表作为一个组合。
对于当前第pos的个数,有两种选择,选或者不选。如果选择这个数,那么调用dfs(pos+1,rest-candidate[pos])进行递归,需满足rest=>candidate[pos].如果不选这个数,则调用dfs(pos+1,rest)进行递归。
在递归开始前,如果rest为零,说明找到了一个和为target的组合,将其放入组合中。每次调用递归函数前,如果我们选了那个数,就要将其放入列表的末尾,该列表就存储我们所选择的所有数,就要将其从列表的末尾处删除。
但根据题目要求,列表中不应该存在重复的组合,因此可以将相同的数放在一起处理,通过哈希映射统计数组candidate中每个数出现的次数,并将统计结果放入一个列表freq中。列表freq的长度即为数组candidate中的不同数的个数。其中每一项对应哈希映射中的一个键值对,即每个书以及它出现的次数,在递归时,对于当前第pos的数,它的值就是freq[pos][0],出现的次数为freq[pos][1],则可以调用
dfs(pos+1, rest-i * freq[pos][0])
即选择了这个数i次且freq[pos][0]应小于rest
时间复杂度:O((2^n)*n)
空间复杂度:O(n)
代码实现:
Arrays.sort(candidates);
for (int num : candidates){
int size = freq.size();
if (freq.isEmpty() || num != freq.get(size -1)[0]){
freq.add(new int[]{num,1});
}else {
++freq.get(size-1)[1];
}
}
dfs(0,target);
return ans;
}
public void dfs(int pos,int rest){
if (rest == 0){
ans.add(new ArrayList<Integer>(sequence));
return;
}
if (pos == freq.size() || rest < freq.get(pos)[0]){
return;
}
dfs(pos+1,rest);
//剪枝
int most = Math.min(rest/ freq.get(pos)[0],freq.get(pos)[1]);
for (int i = 1;i<= most;++i){
sequence.add(freq.get(pos)[0]);
dfs(pos+1,rest-i * freq.get(pos)[0]);
}
for (int i =1;i <= most;++i){
sequence.remove(sequence.size() - 1);
}
}
}