当前位置: 首页 > news >正文

【LeetCode 热题 100】39. 组合总和——(解法一)选或不选

Problem: 39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(S) 或 指数级
    • 空间复杂度:O(T)

整体思路

这段代码旨在解决一个经典的组合搜索问题:组合总和 (Combination Sum)。问题要求从一个无重复元素的正整数数组 candidates 中,找出所有和为 target 的组合。一个关键的特点是,数组中的同一个数字可以被无限制地重复使用。

该算法采用了 深度优先搜索 (DFS) 结合 回溯 (Backtracking) 的策略来系统地探索所有可能的组合。其核心逻辑步骤如下:

  1. 算法核心:递归与回溯

    • 算法的主体是一个名为 dfs 的递归函数。这个函数负责在 candidates 数组中探索所有可能的数字组合。
    • “路径”与“结果”:算法使用一个 path 列表来存储当前正在构建的组合(即从根节点到当前节点的路径),以及一个 ans 列表来存储所有已找到的、符合条件的最终组合。
  2. DFS函数设计

    • dfs(i, ..., target, ...):这个函数的设计非常关键。
      • i:代表当前递归层级可以从 candidates[i] 开始选择数字。这个参数是为了避免产生重复的组合(例如,有了 [2, 2, 3] 之后,就不再需要生成 [2, 3, 2])。通过只向后(或在原地)选择,我们保证了每个组合中的元素是按非递减顺序被选中的。
      • target:表示还需要凑齐的数值。每当选择一个数字,target 就会相应减少。
  3. 递归的终止条件(Base Cases)

    • 成功找到组合if (target == 0)。当 target 减为 0,说明当前 path 中的数字之和恰好等于原始的 target。这是一个有效的组合,因此需要将其拷贝一份new ArrayList<>(path))存入最终结果 ans 中。必须拷贝,因为 path 本身会在后续的回溯过程中被修改。
    • 路径无效if (i == n || target < 0)。当 i == n,表示已经考虑完了所有候选数字,但仍未凑齐 target;当 target < 0,表示当前组合的和已经超出了 target。这两种情况都说明当前路径是无效的,需要终止这条路的探索,直接 return
  4. 探索与回溯(核心选择逻辑)

    • dfs 函数中,对于当前候选数字 candidates[i],我们面临两个选择:
      a. 不选择 candidates[i]:直接跳过当前数字,去考虑下一个数字 candidates[i+1]。这通过递归调用 dfs(i + 1, ...) 来实现。
      b. 选择 candidates[i]
      i. 做选择:将 candidates[i] 添加到当前路径 path 中。
      ii. 继续探索:进行下一步递归调用 dfs(i, ..., target - candidates[i], ...)。注意,这里的第一个参数仍然是 i,而不是 i+1。这正是允许重复使用同一个数字的关键所在。
      iii. 撤销选择(回溯):当上述递归调用返回后(意味着所有从“选择candidates[i]”开始的路径都已探索完毕),必须将刚刚添加的 candidates[i]path 中移除 (path.removeLast())。这个“撤销”操作是回溯的精髓,它使得程序能够返回到上一个状态,去探索其他的可能性(例如,上一个递归层级的不选择分支)。

完整代码

class Solution {/*** 找出所有和为 target 的组合。candidates 中的数字可以被无限制重复使用。* @param candidates 无重复元素的正整数数组* @param target 目标和* @return 所有有效的组合列表*/public List<List<Integer>> combinationSum(int[] candidates, int target) {// ans: 存储所有符合条件的最终组合List<List<Integer>> ans = new ArrayList<>();// path: 存储当前正在构建的组合(路径)List<Integer> path = new ArrayList<>();// 从索引 0 开始进行深度优先搜索dfs(0, candidates, target, ans, path);return ans;}/*** 深度优先搜索(回溯)辅助函数* @param i 当前考虑的候选数字的索引* @param candidates 候选数字数组* @param target 剩余需要凑齐的目标和* @param ans 结果列表* @param path 当前路径*/private void dfs(int i, int[] candidates, int target, List<List<Integer>> ans, List<Integer> path) {int n = candidates.length;// 递归终止条件 1: 索引越界或目标和变为负数,说明此路不通if (i == n || target < 0) {return;}// 递归终止条件 2: 成功找到一个组合if (target == 0) {// 将当前路径的一个快照(副本)加入结果集// 必须创建新列表,因为 path 会在回溯时被修改ans.add(new ArrayList<>(path));return;}// --- 核心选择逻辑 ---// 选择 1: 不使用当前的 candidates[i],直接考虑下一个数字// 递归地探索不包含当前数字的后续组合dfs(i + 1, candidates, target, ans, path);// 选择 2: 使用当前的 candidates[i]// 2a. 做出选择:将当前数字加入路径path.add(candidates[i]);// 2b. 继续探索:递归调用 dfs。//    - 第一个参数仍为 i,表示可以重复使用当前的 candidates[i]。//    - target 减去当前数字的值。dfs(i, candidates, target - candidates[i], ans, path);// 2c. 撤销选择(回溯):将刚才加入的数字移除,以便探索其他分支path.removeLast();}
}

时空复杂度

时间复杂度:O(S) 或 指数级

  1. 时间复杂度分析对于回溯问题通常比较复杂,因为它取决于搜索树的大小。
  2. Ncandidates 的数量,Ttarget
  3. 搜索树的深度最多为 T / min_val,其中 min_valcandidates 中的最小值。在最坏情况下(例如 candidates 中有1),深度可以是 T
  4. 在搜索树的每个节点,我们都可能进行分支。
  5. 因此,总的节点数(即 dfs 的调用次数)是指数级的。一个非常粗略的上界可以是 O(N^T),但这并不精确。更准确的描述是,时间复杂度与解的数量和搜索空间的大小有关。
  6. 在每个找到解的叶子节点,我们需要 O(T) 的时间来复制 path 到结果列表中。
  7. 一个更被接受的表述是,时间复杂度为 O(S),其中 S 是搜索树中节点的总数。这个 S 的值是输入 NT 的指数函数。

空间复杂度:O(T)

  1. 递归栈深度:空间复杂度的主要部分是递归调用栈的深度。在最坏的情况下,例如 candidates 中有 1,我们需要递归 T 次才能使 target 减到 0。因此,递归栈的最大深度是 O(T)
  2. path 列表path 列表存储了当前路径,其最大长度也与递归深度相关,同样是 O(T)
  3. 结果列表 ans:存储最终结果的空间不计入算法的额外辅助空间复杂度。

综合分析
算法所需的额外空间由递归栈和 path 列表决定。因此,空间复杂度为 O(T)

参考灵神

http://www.lryc.cn/news/598745.html

相关文章:

  • windwos11网页切换残留/卡屏/冻结/残影问题
  • Java学习---Spring及其衍生(下)
  • 基于SpringBoot+Vue的电脑维修管理系统(WebSocket实时聊天、Echarts图形化分析)
  • 类和包的可见性
  • 磁性材料如何破解服务器电源高频损耗难题?
  • Linux C 网络基础编程
  • Redis高可用架构演进面试笔记
  • 13-C语言:第13天笔记
  • mysql索引底层B+树
  • HTTP/1.0、HTTP/1.1 和 HTTP/2.0 主要区别
  • OpenLayers 综合案例-基础图层控制
  • 主要分布在背侧海马体(dHPC)CA1区域(dCA1)的位置细胞对NLP中的深层语义分析的积极影响和启示
  • 《Java语言程序设计》第2章复习题(3)
  • 高亮标题里的某个关键字正则表达式
  • JMeter 性能测试实战笔记
  • 云端哨兵的智慧觉醒:Deepoc具身智能如何重塑工业无人机的“火眼金睛”
  • 无人机正摄影像自动识别与矢量提取系统
  • 无人机保养指南
  • 无人机速度模块技术要点分析
  • 04.建造者模式的终极手册:从快餐定制到航天飞船的组装哲学
  • (LeetCode 面试经典 150 题) 56. 合并区间 (排序)
  • Flutter 主流 UI 框架总结归纳
  • 让UV管理一切!!!
  • Django实时通信实战:WebSocket与ASGI全解析(上)
  • 使用钉钉开源api发送钉钉工作消息
  • kafka的shell操作
  • kafka消费者组消费进度(Lag)深入理解
  • 【阿里云-ACP-1】疑难题解析
  • 力扣189:轮转数组
  • Linux基础服务(autofs和Samba)