题目:
与上一题的不同:
1.在上一个“组合总和”中,每个元素可选择无数次,这个题里面,一个元素只能选一次。
2. 上一个题里,元素都不重复,这个题,元素可重复。
思路:和上一题差不多,回朔法。先排序。假如给出的数1,2,2,2。target==5;因为每个数只能选一次,所以要有一个数组visited,数值为1,说明这个下标代表的数已经选过了。不能再选。
第一轮,1加入tmp,之后visited[0]=1。递归调用。递归时,虽然start依然为0,但是,visited[0]==1,已经被访问过,所以不会再被访问,2被加入。。。最后,得出包含“1”的结果。别忘了回溯时,要把tmp里加入的元素踢出来,visited也要设置为0,因为在一轮循环里,可能访问过数组里所有的元素,但是下一轮循环,start加一之后,如果没有设置为0的操作,这个数就无法访问了
第二轮循环:1被踢出去,从2开始,2加入tmp,visited[1]=1。递归调用,找出包含2的结果。。。
补充,因为解集里不能包含重复元素,res里加入tmp时,要用res.contains(tmp);判断tmp这个集合,是否已经在res里面。
class Solution {//回溯法 public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> res =new ArrayList<>(); Arrays.sort(candidates); int[] visited=new int[candidates.length]; backpack(res,candidates,target,0,new ArrayList<Integer>(),visited); return res; } public void backpack(List<List<Integer>> res,int [] candidates,int target,int i,List<Integer> tmp,int[] visited)//i是搜索起点的下标,因为数字可以重复选取,下一层的结点从这个搜索起点开始搜索;这个起点之前的一定被搜索过了,所以不需要 { if(target==0&&!res.contains(tmp)) { res.add(new ArrayList<>(tmp)); return; } if(target<0) return; for(int start=i;start<candidates.length;start++)//一轮循环,找的是tmp里加入了candidates[start]的所有组合。下一轮,找的是,tmp里加入candidates[start+1](candidates[start]不加,因为带这个数的所有组合都找到了)的所有组合。 { if(visited[start]==1)continue;//已访问过的不再访问。 visited[start]=1; tmp.add(candidates[start]); backpack(res,candidates,target-candidates[start],start,tmp,visited); tmp.remove(tmp.size()-1); visited[start]=0; } } }