40,题目:
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.
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基本上是一样的,不同的是这里的数组可能包含重复的数字,但是在求和的过程中不能重复使用数组里的元素。所以首先我们使用上道题目的思路,直接使用回溯法,但是为了解决数组元素不能重复使用的问题,我们需要做一些小的修改,代码如下所示,有两证改进办法:
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates, res, new ArrayList<>(), target, 0);
return res;
}
public void dfs(int[] candidates, List<List<Integer>> res, List<Integer> tmp, int target, int idx){
if(target<0)
return;
else if(target == 0) {
//第一种方法,在将list添加到res之前判断re中是否已经包含,比较直观
if(!res.contains(tmp))
res.add(new ArrayList<>(tmp));
}
else{
for(int i=idx; i<candidates.length; i++) {
//第二种方法,在遍历的过程中加一个判断语句,看前后元素是否相等,如果想等的话则跳过
//if(i > idx && candidates[i] == candidates[i-1]) continue;
tmp.add(candidates[i]);
//此外,为了不使用重复元素,在递归调用时直接将idx+1即可;
dfs(candidates, res, tmp, target-candidates[i], i+1);
tmp.remove(tmp.size()-1);
}
}
}
此外,还有一种改进的方法,这种方法在循环过程中加入了判断语句,可以省去一些不必要的循环。如下所示:
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> results = new ArrayList<>();
calcCombinationSum2(candidates, 0, new int[candidates.length], 0, target, results);
return results;
}
private void calcCombinationSum2(int[] candidates, int cindex, int[] list, int lindex, int target, List<List<Integer>> results) {
if (target == 0) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < lindex; i++) {
result.add(list[i]);
}
results.add(result);
return;
}
int prev = 0;
for (int i = cindex; i < candidates.length; i++) {
if (candidates[i] != prev) {
if (target - candidates[i] < 0) {
break;
}
list[lindex] = candidates[i];
calcCombinationSum2(candidates, i + 1, list, lindex + 1, target - candidates[i], results);
prev = candidates[i];
}
}
}
216, Combination Sum III,题目:
Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
Example 1:
Input: k = 3, n = 7
Output:
[[1,2,4]]
Example 2:
Input: k = 3, n = 9
Output:
[[1,2,6], [1,3,5], [2,3,4]]
这道题目相比前两道就简单了,因为只是求1-9之间和为n的k个数,且不可重复使用。首先仍然使用上述思路,使用回溯法,不同的是,这次既要求元素不可重复使用,又规定了k个数求和的限定条件。所以我们按照下述方法进行修改即可:
public static List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> res = new ArrayList<>();
int[] nums = {1,2,3,4,5,6,7,8,9};
dfs(nums, res, new ArrayList<>(), n, k, 0);
return res;
}
public static void dfs(int[] nums, List<List<Integer>> res, List<Integer> tmp, int target, int k, int index){
//加上k个数的限制,每加一个数,k减1
if(k < 0 || target < 0)
return;
else if(k == 0 && target == 0){
res.add(new ArrayList<>(tmp));
return;
}else{
for(int i=index; i<nums.length; i++) {
tmp.add(nums[i]);
dfs(nums, res, tmp, target - nums[i], k - 1, i + 1);
tmp.remove(tmp.size()-1);
}
}
}
此外我们还可以不用回溯法,直接使用循环的方式求解,代码入下:
public List<List<Integer>> combinationSum3(int k, int n) {
return helper2(k, n, 9);
}
private List<List<Integer>> helper2(int k, int n, int end) {
List<List<Integer>> collection = new ArrayList<List<Integer>>();
//用于记录一次成功的结果
int[] sum = new int[k];
while (true) {
//首先,将1-9分配到sum中,便利所有可能组合,要求是k个数和为n
while (k > 0 && n > 0 && end > 0) {
end = end < n ? end : n;
sum[(k--) - 1] = end;
n -= end--;
}
//如果上面的循环满足了k==0且n==0,则将sum记录到res中
if (k == 0 && n == 0) {
List<Integer> list = new ArrayList<Integer>();
for (int i : sum)
list.add(i);
collection.add(list);
}
//如果sum不满足或者已经记录,则k++,即给sum重新安排数字
if (++k > sum.length)
break;
//将end和n分别更新
end = sum[k - 1];
n += end--;
}
return collection;
}