LeetCode39
目录
- 题目描述
- 示例
- 思路分析
- 代码段
- 代码逐行讲解
- 复杂度分析
- 总结的知识点
- 整合
- 总结
题目描述
给定一个无重复元素的整数数组 candidates
和一个目标整数 target
,找出 candidates
中所有可以使数字和为 target
的组合。candidates
中的数字可以无限制重复选取。
说明:
- 所有数字(包括
target
)都是正整数。 - 解集不能包含重复的组合。
示例
示例 1
输入:
candidates = [2, 3, 6, 7], target = 7
输出:
[
[7],
[2, 2, 3]
]
解释:
- 组合
[7]
的和为 7。 - 组合
[2, 2, 3]
的和为 7。
示例 2
输入:
candidates = [2, 3, 5], target = 8
输出:
[
[2, 2, 2, 2],
[2, 3, 3],
[3, 5]
]
解释:
- 组合
[2, 2, 2, 2]
的和为 8。 - 组合
[2, 3, 3]
的和为 8。 - 组合
[3, 5]
的和为 8。
思路分析
问题核心
我们需要找到所有和为 target
的组合,且每个组合中的数字可以重复使用。
思路拆解
- 深度优先搜索(DFS):
- 使用 DFS 遍历所有可能的组合。
- 剪枝:
- 如果当前数字大于剩余的目标值
target
,则跳过该数字。
- 如果当前数字大于剩余的目标值
- 回溯:
- 在 DFS 过程中,使用
stack
记录当前组合,并在回溯时移除最后一个数字。
- 在 DFS 过程中,使用
- 避免重复组合:
- 通过限制搜索的起始位置(
start
参数),避免生成重复的组合。
- 通过限制搜索的起始位置(
代码段
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
dfs(0, candidates, target, new LinkedList<>(), result);
return result;
}
private void dfs(int start, int[] candidates, int target, LinkedList<Integer> stack, List<List<Integer>> result) {
if (target == 0) {
result.add(new ArrayList<>(stack));
return;
}
for (int i = start; i < candidates.length; i++) {
int candidate = candidates[i];
if (target < candidate) {
continue;
}
stack.push(candidate); dfs(i, candidates, target - candidate, stack, result);
stack.pop();
}
}
}
代码逐行讲解
1. 初始化结果列表
List<List<Integer>> result = new ArrayList<>();
result
用于存储所有符合条件的组合。
2. 调用 DFS
dfs(0, candidates, target, new LinkedList<>(), result);
- 调用 DFS 函数,传入起始位置
0
、数组candidates
、目标值target
、stack
(用于记录当前组合)和结果列表result
。
3. DFS 函数
private void dfs(int start, int[] candidates, int target, LinkedList<Integer> stack, List<List<Integer>> result) {
- DFS 函数的定义,用于递归生成组合。
4. 找到一个组合
if (target == 0) {
result.add(new ArrayList<>(stack));
return;
}
- 如果剩余的目标值
target
为 0,说明找到一个符合条件的组合,将其加入结果列表。
5. 遍历数组
for (int i = start; i < candidates.length; i++) {
- 从
start
开始遍历数组中的每个数字,尝试将其加入当前组合。
6. 剪枝
if (target < candidate) {
continue;
}
- 如果当前数字大于剩余的目标值
target
,则跳过该数字。
7. 加入当前数字
stack.push(candidate);
- 将当前数字加入当前组合。
8. 递归
dfs(i, candidates, target - candidate, stack, result);
- 递归调用 DFS,继续生成下一个数字的组合。
9. 回溯
stack.pop();
- 回溯时移除当前数字,尝试其他可能的组合。
复杂度分析
时间复杂度
- 最坏情况下,每个数字都可以被无限次使用,因此时间复杂度为 O(N^T),其中
N
是数组的长度,T
是目标值。
空间复杂度
- 需要存储所有组合,空间复杂度为 O(M),其中
M
是组合的数量。 - 递归栈的深度为
T / min(candidates)
,因此额外空间复杂度为 O(T / min(candidates))。
总结的知识点
1. 深度优先搜索(DFS)
- 使用 DFS 遍历所有可能的组合。
2. 剪枝
- 在 DFS 过程中,通过条件判断跳过无效的搜索路径。
3. 回溯
- 在 DFS 过程中,使用
stack
记录当前组合,并在回溯时移除最后一个数字。
4. 避免重复组合
- 通过限制搜索的起始位置(
start
参数),避免生成重复的组合。
整合
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>(); // 结果列表
dfs(0, candidates, target, new LinkedList<>(), result); // DFS
return result;
}
private void dfs(int start, int[] candidates, int target, LinkedList<Integer> stack, List<List<Integer>> result) {
if (target == 0) { // 找到一个组合
result.add(new ArrayList<>(stack));
return;
}
// 遍历 candidates 数组
for (int i = start; i < candidates.length; i++) {
int candidate = candidates[i];
if (target < candidate) { // 剪枝
continue;
}
stack.push(candidate); // 加入当前组合
dfs(i, candidates, target - candidate, stack, result); // 递归
stack.pop(); // 回溯
}
}
}
总结
通过 DFS 和回溯,能够高效地找到所有和为 target
的组合。