题目

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

1
2
3
4
5
6
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

1
2
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

1
2
输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

实现代码

使用DFS实现回溯算法 + 排序 + 剪枝优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {

List<List<Integer>> traceList = new LinkedList<>();
LinkedList<Integer> trace = new LinkedList<>();
int sum = 0;

public List<List<Integer>> combinationSum(int[] candidates, int target) {

Arrays.sort(candidates);
backtrack(candidates, target, 0);
return traceList;
}

private void backtrack(int[] candidates, int target, int start) {

if (sum == target) {
traceList.add(new LinkedList<>(trace));
return;
}
for (int i = start; i < candidates.length; i++) {
// 直接返回,因为排了序,右边节点总和值肯定更大
if (sum + candidates[i] > target) {
return;
}
trace.addLast(candidates[i]);
sum += candidates[i];
backtrack(candidates, target, i);
trace.removeLast();
sum -= candidates[i];
}
}
}

核心思路

这道题的关键在于元素可重复使用,但核心还是组合问题。

解决方案:

  1. 排序:将元素从小到大排序,当前元素加上已有和超过target时,后续更大元素也不用考虑
  2. 剪枝优化
    • 当前和超过target时直接返回
    • 当前元素加上已有和超过target时,后续更大元素也不用考虑(因为已排序)
  3. **元素可重复选择:**在递归时将i+1改为i,即包含父节点

时间复杂度

n个元素种找出k个元素的组合,在数学中组合数公式为:C(n, k)

每次traceList.add(new LinkedList<>(trace));的时间为O(k)

所以总的时间复杂度为:O(k*C(n, k ))

空间复杂度

主要的额外空间消耗来自:

  1. traceList:总组合为C(n, k),每个组合k个元素,所以为k*C(n, k)
  2. trace:递归过程中存储当前路径,最大深度为k,空间复杂度为O(k)
  3. 递归调用栈:最大深度为k,空间复杂度为O(k)

因此总的空间复杂度为O(k*C(n, k)),如果不计算输出结果的存储空间,那么额外空间复杂度为O(k)