【回溯-3】39.组合总和

题目描述:

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

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

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

示例 1:

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

示例 2:

复制代码
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

复制代码
输入: candidates = [2], target = 1
输出: []

解题思路:

方法一:回溯 + 剪枝

核心思路:

把问题看成树形结构:

  • 每一层选择一个数字

  • 可以重复选同一个数字

  • 当和等于 target 时,收集结果

  • 当和大于 target 时,剪枝

关键:如何避免重复组合?

用 start 参数控制选择范围:

  • 每次递归时,从 start 开始遍历

  • 选了 candidates[i] 后,下一层从 i 开始(允许重复选当前数字)

  • 但不能选 i 之前的数字(避免重复组合)

具体过程示例:

candidates = [2,3,6,7], target = 7

复制代码
                    []
          /    /     \     \
         2    3       6     7
        /|\   |\      |
       2 3 6  3 6     6
      /|\  |   |
     2 3 6 3  6  6
     |
     2(和=8>7,剪枝)

有效路径:
2→2→3 (和=7) ✅
7 (和=7) ✅

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> result;
        vector<int> path;
        backtrack(candidates, target, 0, path, result);
        return result;
    }
    
private:
    void backtrack(vector<int>& candidates, int target, int start,
                   vector<int>& path, vector<vector<int>>& result) {
        // 终止条件:和等于 target
        if (target == 0) {
            result.push_back(path);
            return;
        }
        
        // 剪枝:和小于 0,直接返回
        if (target < 0) return;
        
        // 从 start 开始遍历,避免重复组合
        for (int i = start; i < candidates.size(); i++) {
            path.push_back(candidates[i]);           // 选择
            backtrack(candidates, target - candidates[i], i, path, result);  // 递归,注意传 i 而不是 i+1
            path.pop_back();                          // 撤销
        }
    }
};

复杂度分析:

设 n 是候选数组长度,target 是目标和。

维度 复杂度 说明
时间复杂度 O(n^(target/min))) 最坏情况,每个位置可以选 n 个数字
空间复杂度 O(target/min) 递归栈深度 + path 长度

更精确:时间复杂度与解的数量和递归深度有关,最坏情况为指数级。

关键细节:

  1. 为什么递归时传 i 而不是 i+1?
  • 传 i:允许重复选当前数字(如 [2,2,3])

  • 传 i+1:不允许重复选(如 40 题「组合总和 II」)

这是本题和 40 题的核心区别。

  1. 为什么用 start 参数?

start 控制当前层从哪个位置开始遍历,避免产生重复组合。

例子 :candidates = [2,3], target = 5

  • 如果不用 start:[2,3] 和 [3,2] 都会出现,重复

  • 用 start:选了 2 后,下一层只能从 2 开始(含 2),不能选 3 之前的

  1. 为什么 target < 0 要返回?

因为和已经超过 target,继续加只会更大,直接剪枝。

  1. 排序优化(可选)

如果先对 candidates 排序,可以在 target < candidates[i] 时提前 break:

cpp 复制代码
sort(candidates.begin(), candidates.end());
// ...
for (int i = start; i < candidates.size(); i++) {
    if (target < candidates[i]) break;  // 后面的更大,直接结束
    // ...
}

方法二:动态规划(完全背包)

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<vector<int>>> dp(target + 1);
        dp[0] = {{}};
        
        for (int c : candidates) {
            for (int j = c; j <= target; j++) {
                for (auto& comb : dp[j - c]) {
                    vector<int> newComb = comb;
                    newComb.push_back(c);
                    dp[j].push_back(newComb);
                }
            }
        }
        
        return dp[target];
    }
};

复杂度:时间 O(n × target × 解的数量),空间 O(target × 解的数量)

缺点:需要存储所有中间结果,空间大。

两种方法对比:

方法 时间复杂度 空间复杂度 推荐度
回溯 + 剪枝 指数级 O(target/min) ⭐⭐⭐⭐⭐
动态规划 O(n × target × 解的数量) O(target × 解的数量) ⭐⭐⭐

总结:

要点 说明
核心思想 回溯:从 start 开始遍历,可以重复选当前数字
关键条件 递归时传 i(允许重复),用 start 避免重复组合
终止条件 target == 0 收集结果,target < 0 剪枝
时间复杂度 指数级
空间复杂度 O(target/min)
相关推荐
Pniubi33 分钟前
关于算法用现成数据结构和手撕
数据结构
唐墨1232 小时前
linux kernel 比较数据大小的宏定义
linux·算法
拂拉氏2 小时前
【知识讲解-题目讲解】 统计满足K约束的子字符串数量2 (滑动窗口+前缀和+二分查找+分析)
算法·前缀和·二分查找·滑动窗口
通信小呆呆7 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
老歌老听老掉牙7 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
笑容掩饰我的失落7 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
Severus_black8 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl
2601_9679791210 小时前
机器学习必修课:经典算法与Python实战
python·算法·机器学习·实战·课程
程序猿打工仔10 小时前
计算机基础(C语言)考编、专技岗
c语言·开发语言·算法
通信小呆呆11 小时前
电子战干扰技术:从“蛮力压制”到“精准欺骗”,再到“认知博弈”
算法·通信·isac