题目:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
说明:
candidates 中的数字可以无限制重复被选取
所有数字(包括 target)都是正整数
解集不能包含重复的组合
示例 :
输入: candidates = [2,1,3], target = 3,
所求解集为:
[
[2,1],
[1,1,1],
[3]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
题目分析
这种搜索题一般都可以暴力搜索(递归),找出所有的组合数,挑选出和为target 的即可,但是很明显这种指数级的增长不是我们想要的,但是我们可以使用一些手段优化递归,例如记忆化搜索,剪枝,使用这种技术可以大大缩短递归调用时间。初步判断这是一道递归+剪枝的题目。做递归题目的第一步就是利用自己的逻辑思维能力和思考能力画出递归的树状图,画的差不多了你对这个题目的理解也就到位了,其次就是涉及好递归函数的参数,递归出口,以及哪里需要剪枝,哪里需要记忆化。
下面我将以上例画出递归树状图
从图中看出符合条件的组合有
[
[2,1],
[1,2],
[1,1,1],
[3]
]
这个结果与 【解集不能包含重复的组合】这个要求不符,解决这一点也很简单,只要做个限制:
即路径上的值只能是非递减的
例如,路径2->2符合,路径2->1不符合,应该剪枝,路径2->3符合条件,路径1->2符合条件,经过这个条件限制后
标为粉色的路径是非法的,这样就去除了重复组合
代码
public class CombinationSum {
public static void main(String[] args) {
CombinationSum cs = new CombinationSum();
List<List<Integer>> list = cs.combinationSum(new int[]{1,2,3},3);
for(int i=0;i<list.size();i++){
for(int j=0;j<list.get(i).size();j++){
System.out.print(list.get(i).get(j)+" ");
}
System.out.println();
}
}
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);//排序加快递归速度
List<Integer> out = new ArrayList<>();
backTrack(candidates,0,target,0,out);
return result;
}
//start这个参数去除重复组合 并且加快递归速度
public void backTrack(int[] candidates, int start,int target,int sum,List<Integer> out){
if(sum==target){//结果集 剪枝
result.add(new ArrayList<Integer>(out));//这里必须new一个
return;
}else{
for(int i=start;i<candidates.length;i++){
//肯定不符合,直接break 剪枝
if (sum + candidates[i] > target) {
break;
}
out.add(candidates[i]);
sum+=candidates[i];
backTrack(candidates,i,target,sum,out);
//一轮搜索结束 需要移除最后一个元素
sum-=candidates[i];
out.remove(out.size()-1);
}
}
}
}
结果: