Every day a Leetcode
题目来源:216. 组合总和 III
解法1:回溯
定义回溯函数 backtrack(upper, curSum),其中 upper 是当前可选的最大数,curSum 是当前组合元素总和。
数组 path 存储了选择的数字,还剩 remain = k - path.size() 个数字要选。
有2种情况,组合一定无法满足要求:
- curSum > n,上溢了。
- 当前还剩 remain 个数要选,要使这些数的和最大,我们选 [upper-remain+1, upper] 范围的 remain 个数即可,其和 sum = (upper-remain+1+upper)*remain/2 = (2 * upper - remain + 1) * remain / 2,当 curSum + sum < n 时,说明curSum 太小了,不管后面怎么选,path 的总和都会小于 n。
当 remain == 0 && curSum == n 时,说明找到了一个组合,将path 添加到 ans 中。
枚举当前选择的数 i,它的范围是 [1, upper],贪心的来讲,我们从大的数开始选比较好。将 i 插入到 path 中,为了不重复,下次可选的最大数变成 i-1,当前组合元素总和变成 curSum + i,进入下一层递归;然后回溯,path 弹出 i。
递归的入口:backtrack(min(9, n), 0)。因为只能使用数字 1 到 9,所以 upper 的取值为 min(9, n),curSum = 0。
代码:
/*
* @lc app=leetcode.cn id=216 lang=cpp
*
* [216] 组合总和 III
*/
// @lc code=start
class Solution
{
public:
vector<vector<int>> combinationSum3(int k, int n)
{
// 特判
if (n < k)
return {};
vector<vector<int>> ans;
vector<int> path;
// upper 是当前可选的最大数,curSum 是当前组合元素总和
function<void(int, int)> backtrack = [&](int upper, int curSum)
{
// 还剩 remain 个数字要选
int remain = k - path.size();
// 剪枝
if (curSum > n || curSum + (2 * upper - remain + 1) * remain / 2 < n)
return;
if (remain == 0 && curSum == n)
{
ans.push_back(path);
return;
}
// 从大的数开始选
for (int i = upper; i >= 1; i--)
{
path.push_back(i);
backtrack(i - 1, curSum + i);
path.pop_back(); // 回溯
}
};
// 入口
backtrack(min(9, n), 0);
return ans;
}
};
// @lc code=end
结果:
复杂度分析:
时间复杂度:O(k*C(9, k))。
空间复杂度:O(k)。