给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。
示例 1:输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
提示:1 <= candidates.length <= 301 <= candidates[i] <= 200candidate 中的每个元素都是独一无二的。1 <= target <= 500
理清题目。算法思想:回溯。算法结构:递归。
1.先对数组做一个升序排序,本意是求相加等于target,换一种思路就是减法的思想,用target一个一个减去数组里的元素,当等于零时就是一组答案,这里选择从大的数开始减,即从数组最后一个元素开始减。
2.既然用到递归,那么就得有递归结束判断条件,否则就会进入死循环。
用target挨个减的过程中,会有大于零、等于零、小于零的结果。
a.如果等于零,则找到一组答案,然后return即可结束。(结束条件1)
b.如果小于零,因为是排好序的数组,再往下找也不会有答案,直接return结束。(结束条件2)
c.如果大于零,那么继续递归。
class Solution {
List<List<Integer>> ans = new ArrayList<>();//全局
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<Integer> list = new ArrayList<>();//用于存储每一个符合的集合
Arrays.sort(candidates);
dfs(candidates.length - 1, target, candidates, list);
return ans;
}
void dfs(int star, int target, int[] nums, List<Integer> list) {
if (target < 0)
return;
else if (target == 0) {
ans.add(new ArrayList<>(list));
return;
} else {
for (int i = star; i >= 0; i--) {
if (target < nums[i])
continue;
list.add(nums[i]);
dfs(i, target - nums[i], nums, list);//每一个元素可重复使用,继续从该元素开始
list.remove(list.size() - 1);//target值小于零return之后,回到上一节点并去掉该节点
}
}
}
}
给定一个数组 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]
]
分析题目,可以看出此题与上一题大同小异,把上一题代码修改即可。不同的地方在于,1.每一个数只能使用一次,不能重复使用;2.数组中可能包含重复数据。
单看第一点,既然数组元素不能重复使用,那么在递归时就让它从下一个元素开始,代码修改:
//原来递归从i开始
dfs(i, target - nums[i], nums, list);
//参数修改为下一个开始遍历,i-1
dfs(i-1, target - nums[i], nums, list);
然后看第二点,数组中可能包含相同元素,当前代码会出现的bug:
bug1.例如示例 1:输入: candidates = [10,1,2,7,6,1,5], target = 8,
排好序后candidates = [1,1,2,5,6,7,10]
解集candidates[0]
,candidates[2],candidates[3]即[1,2,5]
解集candidates[1]
,candidates[2],candidates[3]即[1,2,5]导致重复。
首先想到的解决方法是加一个判定条件,让它跳过该层循环进入下一层循环
if (nums[i] == nums[i + 1])
continue;
然而此时第二个bug出现,
bug2.会错失统计解集candidates[0],candidates[1],candidates[4],[1,1,6]
盗用一个非常完美的解答:同层去重
于是代码修改为:
if (i < star && nums[i] == nums[i + 1])
continue;
//i是当前考察的元素下标,start是本层最开始的那个元素的下标
class Solution {
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<Integer> list = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates.length - 1, target, candidates, list);
return ans;
}
void dfs(int star, int target, int[] nums, List<Integer> list) {
if (target < 0)
return;
else if (target == 0) {
ans.add(new ArrayList<>(list));
} else {
for (int i = star; i >= 0; i--) {
if (target < nums[i])
continue;
if (i < star && nums[i] == nums[i + 1]) //i是当前考察的元素下标,start是本层最开始的那个元素的下标
continue;
list.add(nums[i]);
dfs(i-1, target - nums[i], nums, list);//参数修改为下一个开始遍历,i-1
list.remove(list.size() - 1);
}
}
}
}