一、问题描述
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
二、解题思路
又是一道回溯法解决的排列组合问题,这次的问题相比前一道组合的题目来说要求元素不能重复利用了,即每个元素只能使用一次,因此只需要使用一个boolean型数组visited来记录每个数字是否已经被加入到列表中即可。
由于其他的内容与上一道题目类似,因此回溯的思路在这里就不再介绍了,参照上一篇文章:
在之前的算法基础上,只需要通过visited数组标记已经加入到列表中的元素即可,不过这样还不完整,想象一下数组元素为1,1,2,4,5,在进行递归时最外面一层从1开始,之后进行回溯后最外层会指向第二个元素,也是1,在组合中这样就会造成重复,因此我们还需要一个变量pre来记录上一个加入的元素下标,每次循环比较当前元素和pre位置的元素是否相等,只有当它们不相等时才能加入列表中。
三、代码
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res=new ArrayList();
List<Integer> cur=new ArrayList();
boolean[] visited=new boolean[candidates.length];
Arrays.sort(candidates);
getCombination(res,cur,candidates,target,visited,0);
return res;
}
public void getCombination(List<List<Integer>> res,List<Integer> cur,int[] nums,int target,boolean[] visited,int index){
if(target==0){
res.add(new ArrayList(cur));
return ;
}
if(target<0)return;
int pre=-1;
for(int i=index;i<nums.length&&nums[i]<=target;i++){
if(!visited[i]){
if(pre==-1||nums[i]!=nums[pre]){
pre=i;
cur.add(nums[i]);
visited[i]=true;
getCombination(res,cur,nums,target-nums[i],visited,i);
visited[i]=false;
cur.remove(cur.size()-1);
}
}
}
}
}
四、结果
执行时间 | 3ms | 90.72% |
---|---|---|
消耗内存 | 40M | 32.14% |
五、总结
这两天做了好多回溯算法的问题,对于回溯法的使用也有了一定程度的理解,回溯就是使用递归,在递归的过程中要注意很多细节的地方,很多被忽视的地方往往会造成代码运行错误。下面放置几个链接,都是之前使用的回溯算法的题目,有助于加深理解。