这道题,采用
递归+回溯
,关键是需要进行剪枝
,解集
中不能包含重复组合,怎么剪枝
呢,画出隐形回溯树
可以发现,若同一层的递归数据相等则除了相同数据第一次出现外,其他则跳过这条路径(必须基于数组是排序的)
;continu
为小剪枝,break
为大剪枝
package BDyNamicProgramming;
import sun.reflect.generics.tree.Tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/26 0026 13:29
*/
public class Problem40 {
/**
*
* @param candidates
* @param target
* @return
* 这道题与上一问的区别在于:
*
* 第39题:candidates中的数字可以无限制重复被选取
* 第40题:candidates中的每个数字在每个组合中只能使用一次
*
* 编码的不同之处在于下一层递归的起始索引不一样:
* 第39题:还从候选数组的当前索引值开始
* 第40题:从候选数组的当前索引值的下一位开始
*/
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> rs = new ArrayList<>();
List<Integer> path = new ArrayList<>();
//对数组进行排序:排序视为了提前终止搜索,进行剪枝
Arrays.sort(candidates);
//标注:数组中哪些元素被使用到了:方便去重
// boolean[] used = new boolean[candidates.length];
dfs(candidates,target,path,rs,0);
return rs;
}
public void dfs(int[] candidates,int target,List<Integer> path,List<List<Integer>> rs,int start){
//符合值相等
if(target==0){
//由于r全局只适用到一份,到叶子节点的时候需要左一份拷贝
rs.add(new ArrayList<>(path));
}
if(target<0) return;
//递归加回溯
//在当前的选择下,从start开始进行选择.start(包含)之后的位置都可以进行选择
for(int i=start;i<candidates.length;i++){
//数组中包含重复的元素必须进行去重
//剪枝检测到重复分支的条件:
//1、不是这层的第一个分支
//2、当前选出的数和前一个分支相等
//这个避免重复思想比较重要
//这个方法最重要的作用是,可以让同一层级,不出现相同的元素
//不同级的元素可以出现重复的元素
//continue
if(i>start&&candidates[i]==candidates[i-1]) continue;
//大减职业,因为数组已经进行排序了,当前数据大于target则之后的数必然也大于
//直接跳出剪枝即可
if(candidates[i]>target) break;
path.add(candidates[i]);
//下轮搜索的起点为i+1,因为元素不可以重复使用,这里递归传递下去的是i+1而不是i
dfs(candidates,target-candidates[i],path,rs,i+1);
//进行回溯
path.remove((Object)candidates[i]);
}
}
}