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

};
相关推荐
仰泳的熊猫1 小时前
题目2570:蓝桥杯2020年第十一届省赛真题-成绩分析
数据结构·c++·算法·蓝桥杯
无极低码4 小时前
ecGlypher新手安装分步指南(标准化流程)
人工智能·算法·自然语言处理·大模型·rag
软件算法开发5 小时前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化
superior tigre5 小时前
22 括号生成
算法·深度优先
努力也学不会java6 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
旖-旎7 小时前
二分查找(x的平方根)(4)
c++·算法·二分查找·力扣·双指针
ECT-OS-JiuHuaShan7 小时前
朱梁万有递归元定理,重构《易经》
算法·重构
智者知已应修善业7 小时前
【51单片机独立按键控制数码管移动反向,2片74CH573/74CH273段和位,按键按下保持原状态】2023-3-25
经验分享·笔记·单片机·嵌入式硬件·算法·51单片机
khddvbe8 小时前
C++并发编程中的死锁避免
开发语言·c++·算法