LeetCode Hot100【回溯-39. 组合总和】

题目:39. 组合总和

代码实现
class Solution {
public:
    vector<vector<int>> result;  // 存储所有满足条件的组合
    vector<int> path;           // 当前正在构建的组合

    // 回溯函数:尝试从当前数字开始组合
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum > target) return;  // 当前和大于目标,剪枝
        if (sum == target) {       // 当前和等于目标,记录当前路径
            result.push_back(path);
            return;
        }

        // 从 startIndex 开始,避免重复使用元素
        for (int i = startIndex; i < candidates.size(); i++) {
            sum += candidates[i];    // 加入当前数字
            path.push_back(candidates[i]);  // 将当前数字加入组合
            backtracking(candidates, target, sum, i);  // 继续递归,允许重复使用同一个数字
            sum -= candidates[i];    // 回溯,撤销选择
            path.pop_back();         // 移除最后一个加入的数字
        }
    }

    // 主函数:初始化回溯过程
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        backtracking(candidates, target, 0, 0);  // 从 0 开始回溯
        return result;
    }
};

执行流程

示例输入
candidates = [2,3,6,7], target = 7
回溯过程(树形结构)
target = 7
→ 2 → 2 → 2 → 2 (sum = 8, backtrack)
→ 2 → 2 → 3 (sum = 7, valid) → [2,2,3]
→ 2 → 3 → 3 (sum = 8, backtrack)
→ 2 → 6 (sum = 8, backtrack)
→ 3 → 3 → 3 (sum = 9, backtrack)
→ 7 (sum = 7, valid) → [7]
最终输出
[[2,2,3], [7]]

关键思路

  1. 回溯法的基本框架
    • 从候选数组中选择数字构建当前组合,每次选择后递归尝试继续选数字。
    • 遇到当前组合和大于目标时剪枝,遇到组合和等于目标时记录该组合。
    • 本题允许重复选择同一数字(即 startIndex = i),因此递归中不改变当前索引。
  2. 剪枝条件
    • 如果当前和大于目标(sum > target),则无需继续递归。
  3. 路径记录与回溯
    • 当一个有效路径(和等于目标)找到时,将路径存储在 result 中。
    • 回溯时移除上一个加入的数字,继续尝试其他可能的组合。

时间 & 空间复杂度

操作 时间复杂度 空间复杂度
遍历所有组合 O(2^n) O(n)
  • 时间复杂度: 由于递归过程中每个数字可以被多次使用,且递归树的大小为 O(2^n),其中 n 是候选数组的长度。
  • 空间复杂度: 使用了递归栈,最多需要存储 O(n)的路径(深度)。

基础语法解析

1. sum += candidates[i]sum -= candidates[i]

  • 回溯过程中的加减操作,首先加入当前数字,递归之后撤销该选择。
sum += candidates[i];    // 做选择
sum -= candidates[i];    // 回溯撤销选择

2. path.push_back(candidates[i])path.pop_back()

  • 路径记录,将当前选择加入路径,递归后撤销该选择。
path.push_back(candidates[i]);  // 加入数字
path.pop_back();  // 回溯,移除最后一个数字

3. backtracking(candidates, target, sum, i)

  • 递归调用,每次递归传入当前和 sum,继续构建组合,并允许重复选择当前元素(i 不变)。
backtracking(candidates, target, sum, i);  // 递归,允许重复选择

优化 & 变种

变种 1:去重操作

  • 如果候选数组中有重复数字,通常我们需要在递归中增加去重处理,避免生成重复的组合。
for (int i = startIndex; i < candidates.size(); i++) {
    if (i > startIndex && candidates[i] == candidates[i - 1]) continue;  // 去重
    sum += candidates[i];
    path.push_back(candidates[i]);
    backtracking(candidates, target, sum, i);  // 允许重复选择
    sum -= candidates[i];
    path.pop_back();
}

优化点说明

  • 通过跳过重复的数字,减少不必要的计算。

总结

C++ 语法/结构 作用
backtracking(candidates, target, sum, i) 递归回溯函数
path.push_back(candidates[i]) 选择当前数字,加入路径
sum += candidates[i]; sum -= candidates[i]; 加入和回溯当前数字
时间复杂度: O(2^n) 递归树最大深度为 n
空间复杂度: O(n) 最多保存一个路径

你可能感兴趣的:(LeetCode,Hot100【个人学习】,leetcode,算法,职场和发展)