(LeetCode-Hot100)39. 组合总和

问题简介

LeetCode 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
输出: []

❌|✅|💡|📌 解题思路

本题是一个典型的 回溯算法(Backtracking) 问题,核心在于:

  • 允许重复使用同一元素;
  • 要求所有组合不重复(即 [2,2,3][2,3,2] 视为相同,只保留一种);
  • 找出所有满足条件的组合。

✅ 思路步骤(回溯法)

  1. 排序(可选但推荐) :对 candidates 排序,便于剪枝(提前终止无效路径)。
  2. 定义递归函数
    • 参数:当前路径 path、当前和 sum、起始索引 start(防止重复组合)。
  3. 递归终止条件
    • sum == target:将当前路径加入结果集;
    • sum > target:直接返回(剪枝)。
  4. 遍历选择
    • start 开始遍历 candidates
    • 对每个元素 candidates[i]
      • 加入路径;
      • 递归调用(仍从 i 开始,因为允许重复使用);
      • 回溯(移除刚加入的元素)。

💡 关键点:start 参数确保不会产生重复组合 。例如,若已选 2,后续只能从 2 及之后选,不能回头选 3 再选 2


🔄 其他解法?

  • 动态规划?
    理论上可用 DP 求"是否存在组合",但本题要求 输出所有具体组合 ,DP 难以记录路径,因此 回溯是最自然且高效的方法
  • BFS?
    可行但空间复杂度高,且去重逻辑复杂,不推荐。

结论:回溯法是最佳解法。


❌|✅|💡|📌 代码实现

java 复制代码
class Solution {
    List<List<Integer>> res = new ArrayList<>();


    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates); // 可选,用于剪枝
        backtrack(candidates, target, new ArrayList<>(), 0, 0);
        return res;
    }
    
    private void backtrack(int[] candidates, int target, List<Integer> path, int sum, int start) {
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            if (sum + candidates[i] > target) break; // 剪枝
            path.add(candidates[i]);
            backtrack(candidates, target, path, sum + candidates[i], i); // i 而非 i+1
            path.remove(path.size() - 1);
        }
    }
}
go 复制代码
func combinationSum(candidates []int, target int) [][]int {
    sort.Ints(candidates) // 可选,用于剪枝
    var res [][]int
    var path []int
    var backtrack func(sum, start int)
    backtrack = func(sum, start int) {
        if sum == target {
            res = append(res, append([]int(nil), path...))
            return
        }
        for i := start; i < len(candidates); i++ {
            if sum + candidates[i] > target {
                break // 剪枝
            }
            path = append(path, candidates[i])
            backtrack(sum + candidates[i], i) // i 而非 i+1
            path = path[:len(path)-1]
        }
    }
    
    backtrack(0, 0)
    return res
}

💡 注意 Go 中切片复制:append([]int(nil), path...) 创建新切片,避免引用问题。


❌|✅|💡|📌 示例演示

candidates = [2,3,6,7], target = 7 为例:

复制代码
开始:path=[], sum=0, start=0
├─ 选2 → path=[2], sum=2, start=0
│  ├─ 选2 → [2,2], sum=4, start=0
│  │  ├─ 选2 → [2,2,2], sum=6, start=0
│  │  │  └─ 选2 → sum=8 >7 → 剪枝
│  │  └─ 选3 → [2,2,3], sum=7 → ✅ 加入结果
│  └─ 选3 → [2,3], sum=5, start=1
│     └─ 选3 → sum=8 >7 → 剪枝
├─ 选3 → [3], sum=3, start=1
│  └─ ... 最终无解
└─ 选7 → [7], sum=7 → ✅ 加入结果

最终结果:[[2,2,3], [7]]


❌|✅|💡|📌 答案有效性证明

  1. 完整性 :通过 start 参数确保所有可能组合都被探索(从当前位置及之后选),不会遗漏。
  2. 无重复性 :由于每次递归从 start 开始,不会出现 [2,3][3,2] 这类重复。
  3. 正确性 :只有当 sum == target 时才加入结果,且剪枝保证 sum <= target
  4. 边界处理 :空输入、无解情况(如 target=1, candidates=[2])自然返回空列表。

✅ 满足题目所有要求。


❌|✅|💡|📌 复杂度分析

项目 分析
时间复杂度 O ( N T / M + 1 ) O(N^{T/M + 1}) O(NT/M+1) 其中 N N N 是 candidates 长度, T T T 是 target, M M M 是 candidates 中最小值。 最坏情况下(如 candidates=[1]),递归深度为 target,每层最多 N 个分支。
空间复杂度 O ( T / M ) O(T/M) O(T/M) 递归栈深度最大为 target / min(candidates),即路径最大长度。

💡 实际中由于剪枝,运行效率远高于理论最坏情况。


❌|✅|💡|📌 问题总结

  • 核心思想:回溯 + 剪枝 + 起始索引控制去重。
  • 关键技巧
    • 使用 start 避免重复组合;
    • 排序后提前剪枝(sum + candidates[i] > targetbreak);
    • 注意语言特性(如 Go 切片复制)。
  • 适用场景:求所有满足条件的组合/排列,允许重复元素,需去重。

✅ 掌握此题,可举一反三解决 组合总和 II、III、IV 等变种问题。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
洛_尘2 小时前
测试6:自动化测试--概念篇(JAVA)
java·开发语言·测试
追随者永远是胜利者2 小时前
(LeetCode-Hot100)34. 在排序数组中查找元素的第一个和最后一个位置
java·算法·leetcode·职场和发展·go
爱凤的小光2 小时前
VisionMaster软件---脚本梳理
java·服务器·网络
我命由我123452 小时前
Photoshop - Photoshop 工具栏(63)注释工具
学习·ui·职场和发展·求职招聘·职场发展·学习方法·photoshop
键盘鼓手苏苏4 小时前
Flutter for OpenHarmony:markdown 纯 Dart 解析引擎(将文本转化为结构化 HTML/UI) 深度解析与鸿蒙适配指南
前端·网络·算法·flutter·ui·html·harmonyos
郝学胜-神的一滴5 小时前
当AI遇见架构:Vibe Coding时代的设计模式复兴
开发语言·数据结构·人工智能·算法·设计模式·架构
Frostnova丶10 小时前
LeetCode 190.颠倒二进制位
java·算法·leetcode
骇城迷影10 小时前
代码随想录:链表篇
数据结构·算法·链表