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;
    }

};
相关推荐
智驱力人工智能13 分钟前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
孞㐑¥1 小时前
算法——BFS
开发语言·c++·经验分享·笔记·算法
月挽清风1 小时前
代码随想录第十五天
数据结构·算法·leetcode
XX風1 小时前
8.1 PFH&&FPFH
图像处理·算法
NEXT062 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
代码游侠2 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
想进个大厂2 小时前
代码随想录day37动态规划part05
算法
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章22-Harris角点
图像处理·人工智能·opencv·算法·计算机视觉
子春一2 小时前
Flutter for OpenHarmony:构建一个 Flutter 四色猜谜游戏,深入解析密码逻辑、反馈算法与经典益智游戏重构
算法·flutter·游戏
人道领域3 小时前
AI抢人大战:谁在收割你的红包
大数据·人工智能·算法