题目
这道题给我提供了一个新的思路。就是一旦在深度遍历的时候,出现了重复的问题,那么,可以将出现问题的源头当成一个整体来处理。
给定一个候选人编号的集合 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] ]
思路
一看到这个题,首先想到的就是 DFS。 首先,将数组从小到大排序。定义一个list用于存储当前的路径,它是动态的。定义一个list<list<Integer>>,用来装所有可能的路径。 然后进行DFS。当前选择下标为i的元素,那么下次选择就可以选下标为 i+1,i+2,...len-1 的元素。所有,DFS需要写在for循环中。 并且,需要回溯。所有,将元素添加进列表、下一层dfs、将元素移出列表 这三部分写在循环当中。 这个DFS的条件就是 targer - 每次选择的元素之和 是否是0; 但是需要解决的问题是: 解集不能包含重复的组合! 上述的DFS必然会造成有重复解,因为candidates中可以存在重复元素。 【重点】 如果candidates中的数都是不重复的,上面的思路就没有问题。对于 [1,2,2,2,5],target = 5 来说,当选择了第0号元素和第1号元素,那么接下来可以选第2号元素或第3号元素,这就出现了重复解。 有没有一种可能,我们将把这些重复的元素当成一个整体来处理呢? 我们统计好元素的值与出现的次数,然后按照元素的值的大小排列得到一个二维数组: [ [1,1], 元素1 [2,3], 元素2 [5,1] 元素3 ] 我们在进行DFS时,将相同的元素当成一个整体来处理。当我们面对每一个元素,我们都可以选择是取几个(不超过元素本身数量)这样的元素加入到列表。 ①对于元素1,选择1个。left = 5 - 1*1 = 4 对于元素2,选择1个。left = 4 - 2*1 = 1 对于元素3,不能进行选择 对于元素2,选择2个。left = 4 - 2*2 = 0 --> 将 [1,2,2]加入到结果集 ②对于元素2,选择1个。 left = 5 - 2 = 3 对于元素3,不能进行选择。 对于元素2,选择2个。left = 5 - 2*2 = 1 对于元素3,不能进行选择。 ③对于元素3,选择1个。 left = 5 - 5 * 1 = 0 --> 将[5]加入结果集 也就是说,我们在DFS的时候有两层循环。外层循环是将 <元素值e> 作为遍历元素。 内层循环,是将 i = [1..t] (该元素出现的次数 t) 作为遍历元素。 每次进路径和出路径都同时进 i个e和出i个e 即可。
代码
class Solution {
public List<List<Integer>> res = new ArrayList<>();
public List<Integer> path = new ArrayList<>();
public int[][] num_times;
public int len;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
//统计数组中各个数出现的次数
Map<Integer,Integer> map = new TreeMap<>();
for (int i : candidates){
map.put(i,map.getOrDefault(i,0) + 1);
}
//将map中的键值对转移到数组中
num_times = new int[map.size()][2];
len = map.size();
Set<Integer> set = map.keySet();
Iterator<Integer> it = set.iterator();
int i = 0;
while (it.hasNext()){
Integer key = it.next();
num_times[i][0] = key;
num_times[i][1] = map.get(key);
i++;
}
//dfs
dfs(0,target);
return res;
}
public void dfs(int cur,int left){
if(left < 0){
return;
}
if (left == 0){
ArrayList<Integer> newPath = new ArrayList<>(path);
res.add(newPath);
return;
}
//下一个取到的可能是后面的任意一个元素
for (int i = cur; i < len; i++){
//当前元素取j个
for(int j = 1; j <= num_times[i][1]; j++){
//取不了这么多,就break,也就是说跳过当前键值对
if(left - num_times[i][0] * j < 0){
break;
}
//取当前的值
for (int k = 0; k < j; k++) path.add(num_times[i][0]);
dfs(i + 1,left - num_times[i][0] * j);
//回溯
for (int k = 0; k < j; k++) path.remove(path.size() - 1);
}
}
}
}