引言
子集
- 🎈 题目链接:
- 🎈 做题状态:
我的解题
和上一道全排列的题目有异曲同工之处,都是使用回溯。只不过这个子集,每一层要采用一个起始索引来标识遍历的范围。
class Solution {
public:
void backtrace(vector<vector<int>>& res, vector<int>& path, const vector<int>& nums, int sIndex)
{
res.emplace_back(path);
if (sIndex == nums.size()) return;
for (int i = sIndex; i < nums.size(); ++i)
{
path.emplace_back(nums[i]);
backtrace(res, path, nums, i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
backtrace(res, path, nums, 0);
return res;
}
};
代码优化
✅ 优点分析
- 回溯思路正确:你正确地使用了一个
sIndex
起始索引,避免重复选择,满足子集的非重复性要求。 - 结果收集时机合理:在每次进入递归就收集当前
path
,因为子集允许任意长度(包括空集)。 - 变量命名清晰、可读性好。
🧠 可优化建议
✅ sIndex == nums.size()
的判断其实可以省略
即使当前 sIndex == nums.size()
,下面的 for
循环也不会执行,所以你写的:
if (sIndex == nums.size()) return;
是 可加可不加 的,主要取决于你是否想显式表达“到头了就返回”。如果去掉它,代码更简洁且不会影响结果:
res.emplace_back(path); // 永远在进入递归时记录一次
for (int i = sIndex; i < nums.size(); ++i) {
path.emplace_back(nums[i]);
backtrack(res, path, nums, i + 1);
path.pop_back();
}
✅ 完整优化建议版本
class Solution {
public:
void backtrack(vector<vector<int>>& res, vector<int>& path, const vector<int>& nums, int sIndex) {
res.emplace_back(path);
for (int i = sIndex; i < nums.size(); ++i) {
path.emplace_back(nums[i]);
backtrack(res, path, nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
backtrack(res, path, nums, 0);
return res;
}
};
🧩 与全排列的核心区别
特性 | 全排列 | 子集 |
---|---|---|
是否允许重复选择 | 否 | 否 |
是否使用 used[] | 是 | 否 |
回溯终止条件 | path.size() == nums.size() | 每次进入递归就收集 path |
是否收集不同长度 | 否(只有完整) | 是(所有) |
起始位置控制 | 不需要(所有数字都可选) | 需要(避免重复) |