新博文地址:
[leetcode]Combination SumII
Combination Sum II
Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
Each number in C may only be used once in the combination.
Note:
All numbers (including target) will be positive integers.
Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
The solution set must not contain duplicate combinations.
For example, given candidate set 10,1,2,7,6,1,5 and target 8,
A solution set is:
[1, 7]
[1, 2, 5]
[2, 6]
[1, 1, 6]
Each number in C may only be used once in the combination.
Note:
All numbers (including target) will be positive integers.
Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
The solution set must not contain duplicate combinations.
For example, given candidate set 10,1,2,7,6,1,5 and target 8,
A solution set is:
[1, 7]
[1, 2, 5]
[2, 6]
[1, 1, 6]
具体思路跟Combination Sum 差不多,只不过不允许重复,只能从数组中拿数字,这就增加了一点难度,如果单纯的用dfs,如何避免出现[1,2,3,3,3,4,5] - 6中找到了[1,2,3][1,2,3][1,2,3][3,3][3,3],[3,3]等重复组合?
我本来尝试接触所有子集,并且从中寻找和为target的子集。这种思路很暴力,但是好在简单,但是时间复杂度已经高达O(n!),必挂无疑。不过还是可以作为参考的。
这个是TLE代码,不感兴趣就直接跳过
public List<List<Integer>> combinationSum2(int[] num, int target) { List<List<Integer>> result = new ArrayList<List<Integer>>(); Arrays.sort(num); int length = num.length - 1; for(; length >= 0 && num[length] > target ;length--); if(length <= 0){ return result; } int[] newNum = Arrays.copyOf(num, length + 1); Set<List<Integer>> combination = getCombination(newNum,target); for(List<Integer> list : combination){ if(getSum(list) == target){ result.add(list); } } return result; } private int getSum(List<Integer> list){ int sum = 0; for(int tem : list){ sum += tem; } return sum; } private Set<List<Integer>> getCombination(int[] num,int target){//时间复杂度太大了 Set<List<Integer>> combination = new HashSet<List<Integer>>(); if(num.length == 1){ List<Integer> list = new ArrayList<Integer>(); list.add(num[0]); combination.add(list); return combination; } int[] preNum = Arrays.copyOf(num, num.length - 1); Set<List<Integer>> preCombination = getCombination(preNum,target); for(List<Integer> list : preCombination){ List<Integer> copy = new ArrayList<Integer>(list); if(getSum(copy) < target){ copy.add(num[num.length - 1]); combination.add(copy); } } List<Integer> singleElement = new ArrayList<Integer>(); singleElement.add(num[num.length - 1]); combination.addAll(preCombination); combination.add(singleElement); return combination; }
这里没有考虑空集,因为空集肯定不满足条件,当然别的情况下,可能需要考虑空集。
毫无疑问,该算法是个坑。
因此考虑Combination Sum 中的算法,依然DFS,并在DFS中适当的剪枝,比如上例:在1,2,3,3,3,4,4,5,5,5,6, 查找和为7的子集,当我们找到4的时候,发现1234满足条件,那么就直接退出该层递归。因为后面的要么还是1234 = 7,要么>7。然后退到了index = 2(num = 3)的这层递归,看到3的后面还是3,直接跳过,因为如果index = 3的这个3,这一层如果能找到合法序列,那么在index=2这个3的这一层,必然能找到相同的合法序列。即,前者是完全cover后者的,因此我们向后找,找到第一个不为3的数字4,既然从4开始dfs,可能比较绕,仔细想一下不难明白。
代码如下:
List<List<Integer>> result = new ArrayList<List<Integer>>(); public List<List<Integer>> combinationSum2(int[] num, int target) { Arrays.sort(num); int length = num.length -1; for(; length >= 0 && num[length] > target ;length--); if(length < 0){ return result; } int[] newNum = Arrays.copyOf(num, length + 1); //以上只是做了一个简答的预处理,完全可以不写,对复杂度也无影响 for(int i = 0 ; i < newNum.length; i++){ dfs(new ArrayList<Integer>(),newNum,i,target); for(int j = i + 1; j < newNum.length && newNum[j] == newNum[i];j++,i++); } return result; } private void dfs(List<Integer> list,int[] num ,int index,int target){ if(index >= num.length){ return; } if(num[index] == target){ List<Integer> copy = new ArrayList<Integer>(list); copy.add(target); result.add(copy); } if(num[index] >= target){ return; } list.add(num[index]); for(int j = index + 1; j < num.length; j++){ dfs(list, num, j, target - num[index]); if(num[j] >= target - num[index]){//dont search the same combination break; } for(int k = j + 1; k < num.length && num[k] == num[j];k++,j++); } list.remove(list.size() - 1); }