给定一个数组 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]
]
这道题和 「39. 组合总和(中等)」 几乎一样。
唯一的不同是这题每个数只能使用一次,而 「39. 组合总和(中等)」 中可以使用无限次。
我们再来回顾一下应该如何快速判断一道题是否应该使用 DFS + 回溯算法来爆搜。
这个判断方法,最早三叶在 【刷穿 LeetCode】37. 解数独(困难) 讲过。
总的来说,你可以从两个方面来考虑:
到
,而 DFS + 回溯的话,通常会限制在 30 以内。
这道题数据范围是 30 以内,而且是求所有方案。因此我们使用 DFS + 回溯来求解。
我们可以接着 「39. 组合总和(中等)」 的思路来修改:
[1,2,1]
和 [1,1,2]
,set 不会认为是相同的数组。class Solution {
public List<List<Integer>> combinationSum2(int[] cs, int t) {
Arrays.sort(cs);
Set<List<Integer>> ans = new HashSet<>();
List<Integer> cur = new ArrayList<>();
dfs(cs, t, 0, ans, cur);
return new ArrayList<>(ans);
}
/**
* cs: 原数组,从该数组进行选数
* t: 还剩多少值需要凑成。起始值为 target ,代表还没选择任何数;当 t = 0,代表选择的数凑成了 target
* u: 当前决策到 cs[] 中的第几位
* ans: 最终结果集
* cur: 当前结果集
*/
void dfs(int[] cs, int t, int u, Set<List<Integer>> ans, List<Integer> cur) {
if (t == 0) {
ans.add(new ArrayList<>(cur));
return;
}
if (u == cs.length || t < 0) return;
// 使用 cs[u]
cur.add(cs[u]);
dfs(cs, t - cs[u], u + 1, ans, cur);
// 进行回溯
cur.remove(cur.size() - 1);
// 不使用 cs[u]
dfs(cs, t, u + 1, ans, cur);
}
}
这是我们「刷穿 LeetCode」系列文章的第 No.40
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
由于 LeetCode 的题目随着周赛 & 双周赛不断增加,为了方便我们统计进度,我们将按照系列起始时的总题数作为分母,完成的题目作为分子,进行进度计算。当前进度为 40/1916
。
为了方便各位同学能够电脑上进行调试和提交代码,我在 Github 建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和一些其他的优选题解。