题目描述
找出所有相加之和为 n的 k个数的组合,且满足下列条件:
- 只使用数字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 <= 91 <= n <= 60
解决方案:
这段代码的核心功能是找出所有由 k 个不同的 1~9 数字组成、和为 n 的组合(combinationSum3 问题),采用「类成员变量 + 倒序枚举 + 精准剪枝」的回溯思路实现,既保留了你习惯的代码风格,又复用了高效的剪枝逻辑,是该问题的最优解法之一。
核心逻辑
-
成员变量作用:
path:临时数组,存储当前正在构造的数字组合(比如选了 9、2 后,path 为 [9,2]);ans:最终结果数组,存储所有符合 "k 个数、和为 n、1~9 不重复" 的组合。
-
递归函数
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后返回。 - 核心流程(倒序枚举 + 回溯):① 遍历从
i到d的所有数字 j(倒序选数避免重复组合,j≥d 保证能凑够 d 个数,比如 d=2 时 j 最小为 2,因为 2+1=3 能凑 2 个数);② 选数字 j:将 j 加入path,递归调用dfs(j-1, k, n-j)(下一轮只能选更小的数,保证数字不重复);③ 回溯:递归返回后执行path.pop_back(),删掉刚加入的数字,尝试下一个可选数字。
- 参数说明:
-
主函数
combinationSum3:- 从数字上界 9 开始调用
dfs(初始可选数字为 1~9),启动组合构造过程; - 最终返回存储所有合法组合的
ans。
- 从数字上界 9 开始调用
关键特点
- 高效剪枝:通过 "剩余和的最大 / 最小范围" 判断,大幅减少递归次数,效率远高于无剪枝的暴力枚举;
- 天然去重:倒序枚举 + 下一轮选更小的数,保证组合内数字递减(如 [9,2] 而非 [2,9]),避免重复组合(组合不考虑顺序);
- 范围合规:初始 i=9,限制了数字只能是 1~9,符合题目要求。
总结
- 核心思路:递归倒序枚举 1~9 的数字,通过精准剪枝提前终止无效递归,回溯遍历所有 "k 个数、和为 n" 的合法组合;
- 关键操作:剪枝公式是效率核心,倒序枚举 + j≥d 是去重和避免无效枚举的关键,
push_back/pop_back实现回溯; - 功能效果:能输出所有符合条件的组合,无重复、无遗漏,且时间效率最优。
以输入k=3, n=9为例,最终会生成[[6,2,1],[5,3,1],[4,3,2]](倒序枚举生成的组合为降序,和正序结果等价),完全符合题目要求。
函数源码:
cppclass 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; } };