原题链接
给定一个数组 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]
]
Related Topics 数组 回溯算法
613 0
先画出递归树, 思考每个节点的状态值, 应该包含的变量
这里与 算法——LeetCode39. 组合总和 的区别:
39题:元素可以重复使用,组合不能重复。
本题:元素不可以重复使用,组合不能重复。
所以需要注意以下几点:
给定的数组可能有重复的元素,先排序,使得重复的数字相邻,方便去重。
for 枚举出选项时,加入下面判断,从而忽略掉同一层重复的选项,避免产生重复的组合。比如[1,2,2,2,5],选了第一个 2,变成 [1,2],它的下一选项也是 2,跳过它,因为如果选它,就还是 [1,2]。
if (i - 1 >= index && candidates[i - 1] == candidates[i]) {
continue;
}
i+1
,避免与当前选的i
重复。dfs(i + 1, remain - candidates[i], paths);
代码:
class Solution {
List<List<Integer>> ans;
int[] candidates;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
ans = new ArrayList<>();
this.candidates = candidates;
// 关键步骤, 排序, 去重的关键
Arrays.sort(candidates);
dfs(0, target, new ArrayList<>());
return ans;
}
/**
* @param index 从候选数组的 index 位置开始搜索, 下一个要添加的元素索引
* @param remain 剩余的目标值
* @param paths 当前结点包含的元素值, 从根结点到叶子结点的路径
*/
public void dfs(int index, int remain, List<Integer> paths) {
if (remain == 0) {
ans.add(new ArrayList(paths));
return;
}
// 否则向下层递归, 每次 index 节点为未添加的值, 下一层需要添加
for (int i = index; i < candidates.length; i++) {
// 因为数组已经排序, 所以后面的也会是负数, 这里不再向下递归, break退出循环
if (remain - candidates[i] < 0) {
break;
}
// 重要: 剪枝操作, 由于数组经过了排序, 所以若有重复元素, 选第一个进行递归即可,
// 后面的舍去不递归, 因为第一个已经包含了后面的情况
if (i > index && candidates[i] == candidates[i - 1]) {
continue;
}
paths.add(candidates[i]);
// 注意这里递归的参数设置
dfs(i + 1, remain - candidates[i], paths);
paths.remove(paths.size() - 1);
}
}
}
或者可以将 remain< 放到循环外, 即下一层去做, 但会多递归一层, 代码如下:
class Solution {
List<List<Integer>> ans;
int[] candidates;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
ans = new ArrayList<>();
this.candidates = candidates;
// 关键步骤, 排序, 去重的关键
Arrays.sort(candidates);
dfs(0, target, new ArrayList<>());
return ans;
}
/**
* @param index 从候选数组的 index 位置开始搜索, 下一个要添加的元素索引
* @param remain 剩余的目标值
* @param paths 当前结点包含的元素值, 从根结点到叶子结点的路径
*/
public void dfs(int index, int remain, List<Integer> paths) {
if (remain == 0) {
ans.add(new ArrayList(paths));
return;
}
if (remain < 0) {
return;
}
// 否则向下层递归, 每次 index 节点为未添加的值, 下一层需要添加
for (int i = index; i < candidates.length; i++) {
// 重要: 剪枝操作, 由于数组经过了排序, 所以若有重复元素, 选第一个进行递归即可,
// 后面的舍去不递归, 因为第一个已经包含了后面的情况
if (i > index && candidates[i] == candidates[i - 1]) {
continue;
}
paths.add(candidates[i]);
// 注意这里递归的参数设置
dfs(i + 1, remain - candidates[i], paths);
paths.remove(paths.size() - 1);
}
}
}
参考:
回溯算法 + 剪枝(Java、Python)
手画图解 | 长文漫谈回溯 | 40. 组合总和 II