引言
组合总和
- 🎈 题目链接:
- 🎈 做题状态:
我的解题
这道题有一个要注意的地方就是,这个是组合,顺序不同的组合是一种结果,例如 [2, 3, 2] 和 [3, 2, 2] 是一种结果,所以这个在遍历每一层决策树的时候就需要注意遍历的起始位置了。因为这个遍历顺序不同的也是同一种组合,所以在遍历的时候要使用一个起始索引,同一层中,这个起始索引是依次增加的。而且由于同一个数可以使用多次,所以下一层的第一个起始索引和其父节点的起始索引是相同的。
class Solution {
private:
void backtrack(vector<vector<int>>& res, vector<int>& path, int& sum, vector<int>& candidates, int target, int startIndex)
{
if (sum > target) return;
if (sum == target)
{
res.emplace_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); ++i)
{
sum += candidates[i];
path.emplace_back(candidates[i]);
backtrack(res, path, sum, candidates, target, i);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> path;
int sum = 0;
backtrack(res, path, sum, candidates, target, 0);
return res;
}
};
代码和思路分析
代码实现得很扎实,整体逻辑清晰、思路正确,非常接近 LeetCode 题解中推荐的写法。下面从思路讲解、代码优点、可选优化点这几个方面来详细说明。
✅ 代码思路解析
准确抓住了这道题的关键:
-
组合顺序无关:
[2, 2, 3]
和[3, 2, 2]
被视为相同组合,所以需要用startIndex
来避免重复排列。 -
回溯树模型:
- 每一层代表当前选择的一个数字。
- 因为可以重复选取相同数字,所以下一层递归仍然从当前
startIndex
开始。 - 如果不能重复,下一层应该是
i + 1
。
-
剪枝逻辑:
- 当前累计和
sum
超过target
时立即返回,避免无效递归。
- 当前累计和
-
结果收集时机:
- 当
sum == target
,说明组合有效,加入结果集。
- 当
✅ 代码优点总结
优点 | 说明 |
---|---|
✅ 回溯逻辑正确 | 处理了重复元素、组合去重 |
✅ 使用 sum 提前剪枝 | 减少递归深度 |
✅ 变量命名清晰 | sum , path , startIndex 表意准确 |
✅ 使用引用传参 | 提升性能,避免拷贝 |
🧠 可选优化建议(不是必须)
✅ 1. 移除 sum
变量,使用参数传值(更简洁)
现在维护了一个外部 sum
,虽然效率上没问题,但逻辑上更自然的写法是通过参数值传递:
void backtrack(vector<vector<int>>& res, vector<int>& path, vector<int>& candidates, int target, int startIndex) {
if (target < 0) return;
if (target == 0) {
res.emplace_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); ++i) {
path.emplace_back(candidates[i]);
backtrack(res, path, candidates, target - candidates[i], i);
path.pop_back();
}
}
这样不需要维护一个额外的 sum
,逻辑上更聚焦于“剩余的目标值”。
➡️ 优点:变量更少、代码更纯净。
✅ 2. 提前对 candidates
排序,并加入剪枝
在实际题目中,如果 candidates
无序,你可以先排序,再剪枝:
if (candidates[i] > target) break; // 提前终止循环
➡️ 这样能进一步减少无效递归分支(不过 LeetCode 上这题不要求排序)。
✅ 最终推荐优化版本(等效逻辑,更紧凑)
class Solution {
private:
void backtrack(vector<vector<int>>& res, vector<int>& path, vector<int>& candidates, int target, int startIndex) {
if (target < 0) return;
if (target == 0) {
res.emplace_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); ++i) {
path.emplace_back(candidates[i]);
backtrack(res, path, candidates, target - candidates[i], i); // 可以重复选
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> path;
backtrack(res, path, candidates, target, 0);
return res;
}
};