day156—回溯—组合总和(LeetCode-216)

题目描述

找出所有相加之和为 nk个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

复制代码
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

复制代码
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

复制代码
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

解决方案:

这段代码的核心功能是找出所有由 k 个不同的 1~9 数字组成、和为 n 的组合(combinationSum3 问题),采用「类成员变量 + 倒序枚举 + 精准剪枝」的回溯思路实现,既保留了你习惯的代码风格,又复用了高效的剪枝逻辑,是该问题的最优解法之一。

核心逻辑

  1. 成员变量作用

    • path:临时数组,存储当前正在构造的数字组合(比如选了 9、2 后,path 为 [9,2]);
    • ans:最终结果数组,存储所有符合 "k 个数、和为 n、1~9 不重复" 的组合。
  2. 递归函数 dfs 逻辑

    • 参数说明:
      • i:当前可选数字的上界(只能从 1~i 中选数,初始为 9,符合 1~9 的范围限制);
      • k:需要选取的数字总个数(全程不变);
      • n:剩余需要凑的和(每选一个数 j,就减去 j)。
    • 核心剪枝(提前终止无效递归):if (n < 0 || n > (i * 2 - d + 1) * d / 2)
      • n < 0:剩余和为负,不可能凑出合法组合;
      • n > (i*2 -d +1)*d/2:剩余 d 个数的最大可能和(从 i 倒着选 d 个不同数的和,如 i=9、d=2 时最大和 = 9+8=17),超过则直接剪枝,避免无效枚举。
    • 终止条件:if (k == path.size()):当前组合的长度等于 k,说明已选够 k 个数(剪枝已保证剩余和为 0),将path加入ans后返回。
    • 核心流程(倒序枚举 + 回溯):① 遍历从id的所有数字 j(倒序选数避免重复组合,j≥d 保证能凑够 d 个数,比如 d=2 时 j 最小为 2,因为 2+1=3 能凑 2 个数);② 选数字 j:将 j 加入path,递归调用dfs(j-1, k, n-j)(下一轮只能选更小的数,保证数字不重复);③ 回溯:递归返回后执行path.pop_back(),删掉刚加入的数字,尝试下一个可选数字。
  3. 主函数 combinationSum3

    • 从数字上界 9 开始调用dfs(初始可选数字为 1~9),启动组合构造过程;
    • 最终返回存储所有合法组合的ans

关键特点

  • 高效剪枝:通过 "剩余和的最大 / 最小范围" 判断,大幅减少递归次数,效率远高于无剪枝的暴力枚举;
  • 天然去重:倒序枚举 + 下一轮选更小的数,保证组合内数字递减(如 [9,2] 而非 [2,9]),避免重复组合(组合不考虑顺序);
  • 范围合规:初始 i=9,限制了数字只能是 1~9,符合题目要求。

总结

  1. 核心思路:递归倒序枚举 1~9 的数字,通过精准剪枝提前终止无效递归,回溯遍历所有 "k 个数、和为 n" 的合法组合;
  2. 关键操作:剪枝公式是效率核心,倒序枚举 + j≥d 是去重和避免无效枚举的关键,push_back/pop_back实现回溯;
  3. 功能效果:能输出所有符合条件的组合,无重复、无遗漏,且时间效率最优。

以输入k=3, n=9为例,最终会生成[[6,2,1],[5,3,1],[4,3,2]](倒序枚举生成的组合为降序,和正序结果等价),完全符合题目要求。

函数源码:

cpp 复制代码
class Solution {
public:
    vector<int>path={};
    vector<vector<int>>ans={};
    
    void dfs(int i, int k, int n) {
        int d = k - path.size(); // 还需要选d个数
       
        if (n < 0 || n > (i * 2 - d + 1) * d / 2) {
            return;
        }
        // 终止条件:选够k个数(d=0),且剩余和为0(隐含在剪枝里)
        if (k == path.size()) {
            ans.emplace_back(path);
            return;
        }
        // 从i倒着枚举到d(保证有足够数可选)
        for (int j = i; j >= d; j--) {
            path.push_back(j);          // 选当前数j
            dfs(j - 1, k, n - j);       // 下一轮从j-1开始选,剩余和减j
            path.pop_back();        
        }
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(9, k, n);
        return ans;
    }

};
相关推荐
努力学算法的蒟蒻2 小时前
day60(1.19)——leetcode面试经典150
算法·leetcode·面试
且去填词2 小时前
三色标记法与混合写屏障:Go GC 垃圾回收全流程解析
开发语言·算法·golang·三色标记法·gogc·屏障技术
漫随流水2 小时前
leetcode回溯算法(216.组合总和Ⅲ)
数据结构·算法·leetcode·回溯算法
Leweslyh2 小时前
【实战】设计一颗“永远向阳”且“姿态稳定”的卫星 (例题 4.8)
算法·航天·轨道力学·星际航行·太阳同步轨道
木木木一2 小时前
Rust学习记录--C12 实例:写一个命令行程序
学习·算法·rust
大柏怎么被偷了2 小时前
【C++】哈希桶
数据结构·算法·哈希算法
leaves falling2 小时前
c语言自定义类型深度解析:联合(Union)与枚举(Enum)
c语言·开发语言·算法
期末考复习中,蓝桥杯都没时间学了2 小时前
力扣刷题记录2
算法·leetcode·职场和发展
高洁012 小时前
知识图谱如何结合 RAG实现更精确的知识问答
人工智能·算法·机器学习·数据挖掘·知识图谱