手写回溯生成K组合的关键三步是:①递归终止条件为current.size() == k;②选择范围控制为从start开始遍历,下层start设为i + 1;③回溯操作为push_back后递归、pop_back恢复状态。

用 std::next_permutation 生成组合容易出错吗?
容易。它本意是生成「全排列」,强行用于组合会绕弯子:得先构造一个含 K 个 true 和 N−K 个 false 的布尔数组,再靠 std::next_permutation 枚举所有真假排列,每次遍历下标把 true 对应位置的元素挑出来——逻辑冗余、可读性差,且一旦数组有重复值还可能漏解或重解。
更直接的做法是手写回溯,或用标准库中更贴合语义的方案(比如 C++23 的 std::ranges::combinations,但目前主流编译器尚未完全支持)。
手写回溯生成 K 组合,关键三步是什么?
核心是维护当前已选元素的容器(如 vector<int></int>),递归地从某个起始位置往后选,避免重复和顺序颠倒:
- 递归终止条件:
current.size() == k,此时保存一份拷贝到结果中 - 选择范围控制:从
start开始遍历,每次选一个数后,下一层递归的start设为i + 1(不是start + 1),确保不重用同一位置,也不产生逆序组合(如 [2,1] 不会出现) - 回溯操作:进入下层前
push_back,返回前pop_back,保持current状态干净
示例片段(对 nums = {1,2,3,4} 选 2 个):
立即学习“C++免费学习笔记(深入)”;
void backtrack(vector<int>& nums, int k, int start, vector<int>& current, vector<vector<int>>& res) {
if (current.size() == k) {
res.push_back(current);
return;
}
for (int i = start; i < nums.size(); i++) {
current.push_back(nums[i]);
backtrack(nums, k, i + 1, current, res); // 注意是 i + 1
current.pop_back();
}
}
如果数组含重复元素,怎么去重?
原始回溯会输出重复组合(如输入 {1,1,2}、k=2,得到两组 [1,1] 或 [1,2] 多次)。去重不能只靠 set<vector<int>>,那会拖慢性能且掩盖逻辑问题。
正确做法是在搜索前排序,然后在循环内跳过「相同值且非首次出现」的元素:
- 加一句
if (i > start && nums[i] == nums[i-1]) continue; - 前提是
nums已调用sort(nums.begin(), nums.end()) - 注意判断条件必须是
i > start,而非i > 0,否则会错误剪掉同一层首次出现的重复值
性能与边界要注意什么?
组合数是 C(n,k),当 n 较大(如 n=30, k=15)时,结果数量超 1.5 亿,内存和时间都会爆炸——这不是算法写得不对,而是问题本身不可行。
- 实际使用前务必检查
k是否在[0, n]范围内,k == 0应返回一个空组合{},k > n直接返回空结果 - 避免传
vector值参;递归中传引用(const vector<int>&),结果用移动语义接收(res.push_back(move(current))可省一次拷贝) - 若只需遍历而非存储全部结果,把处理逻辑(如打印、校验)直接塞进递归终止分支,避免堆存大量中间数据
真正卡住人的往往不是“怎么写出第一个版本”,而是没想清楚输入规模是否合理、重复逻辑有没有被误剪、以及 start 和 i+1 这种细节写反了导致死循环或漏解。


















