1.题目描述:
给定一个候选人编号的集合candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。candidates中的每个数字在每个组合中只能使用一次。注意:解集不能包含重复的组合。
2.当开始写的回溯代码(已完成剪枝):
类似的题型做多了直接按照模板写。一跑发现有重复的结果。原因是此题数组元素会有重复的,[10,1,2,7,6,1,5]排序后[1,1,2,5,6,7,10],[1,2,5]和[1,7]会出现两次,这里的1两次分别是索引0和索引1的位置,可以在最后的resList中去重但是太麻烦。对数组排序之后,去重只要找到元素相等的地方。会发现[1,1,6]只有一次,也就是纵向/递归往下/树枝上允许出现重复元素,但是横线/同一树层上不允许出现重复,否则就会有我上述跑完结果(同一for循环中若有相同元素,那么符合条件的话必然也是两份)。
class Solution {
private List> resList = new ArrayList<>();
private List list = new ArrayList<>();
private int sum = 0;
public List> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return resList;
}
public void backTracking(int[] candidates, int target, int index) {
if (sum == target) {
resList.add(new ArrayList<>(list));
return;
}
for (int i = index; i < candidates.length; i++) {
if (sum + candidates[i] > target) break;//剪枝
list.add(candidates[i]);
sum += candidates[i];
backTracking(candidates, target, i + 1);
list.remove(list.size() - 1);
sum -= candidates[i];
}
}
}
3.利用index去重(推荐):
class Solution {
private List> resList = new ArrayList<>();
private List list = new ArrayList<>();
private int sum = 0;
public List> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return resList;
}
public void backTracking(int[] candidates, int target, int index) {
if (sum == target) {
resList.add(new ArrayList<>(list));
return;
}
for (int i = index; i < candidates.length; i++) {
if (i > index && candidates[i] == candidates[i - 1]) continue;//去重,i > index,防止空指针也防止[1,1,6]放不进结果集,不能i > 0开始;这里操作candidates[i - 1]树枝时已经把一份结果全存入
if (sum + candidates[i] > target) break;//剪枝
list.add(candidates[i]);
sum += candidates[i];
backTracking(candidates, target, i + 1);
list.remove(list.size() - 1);
sum -= candidates[i];
}
}
}
二刷:
class Solution {
List> res = new ArrayList<>();
List list = new ArrayList<>();
public List> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return res;
}
public void backTracking(int[] candidates, int target, int index) {
int sum = 0;
for (Integer item : list) sum += item;
if (sum == target) res.add(new ArrayList<>(list));
if (sum >= target) return;
for (int i = index; i < candidates.length && sum + candidates[i] <= target; i++) {
if (i > index && candidates[i] == candidates[i - 1]) continue;
list.add(candidates[i]);
backTracking(candidates, target, i + 1);
list.remove(list.size() - 1);
}
}
}
4.使用标记数组去重:
class Solution {
private List> resList = new ArrayList<>();
private List list = new ArrayList<>();
private int sum = 0;
private boolean[] flag;
public List> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
flag = new boolean[candidates.length];//标记数组帮助判断同一树层元素是否已经被访问
backTracking(candidates, target, 0);
return resList;
}
public void backTracking(int[] candidates, int target, int index) {
if (sum == target) {
resList.add(new ArrayList<>(list));
return;
}
for (int i = index; i < candidates.length; i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && !flag[i - 1]) continue;//去重,这里i > 0没问题,因为有标记的判断
//flag[i - 1] == true,说明纵向同一树枝candidates[i - 1]使用过
//flag[i - 1] == false,说明横向同一树层candidates[i - 1]使用过,但是被回溯复原
if (sum + candidates[i] > target) break;//剪枝
list.add(candidates[i]);
flag[i] = true;
sum += candidates[i];
backTracking(candidates, target, i + 1);
list.remove(list.size() - 1);//回溯
sum -= candidates[i];//回溯
flag[i] = false;//回溯
}
}
}
5.另一种使用标记数组去重(推荐):
上一种标记数组记录了位置而非值(排序后的原因),这种依据每个位置的值(无需排序,排序为了剪枝)来去重,和leetcode491. 递增子序列去重方法一致,但是子集问题必须先排序(不排序会出现虽是同一树层上取不重复值的情况,担仍出现重复子集)!
class Solution {
private List> resList = new ArrayList<>();
private List list = new ArrayList<>();
private int sum = 0;
public List> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return resList;
}
public void backTracking(int[] candidates, int target, int index) {
if (sum == target) {
resList.add(new ArrayList<>(list));
return;
}
boolean[] flag = new boolean[51];//每层更新
for (int i = index; i < candidates.length; i++) {
if (flag[candidates[i]]) continue;//去重
if (sum + candidates[i] > target) break;//剪枝
list.add(candidates[i]);
flag[candidates[i]] = true;//无需回溯
sum += candidates[i];
backTracking(candidates, target, i + 1);
list.remove(list.size() - 1);//回溯
sum -= candidates[i];//回溯
}
}
}