next_permutation 返回 false 表示已无更大排列,自动重置为最小排列,并非报错;需先排序再用 do-while 枚举全部排列;求组合数应避免阶乘,改用递推公式 c(n,k)=c(n,k−1)×(n−k+1)/k 并利用对称性优化。

next_permutation 为什么返回 false 却没报错
它不是报错函数,而是按字典序生成下一个排列后返回 true;如果当前已是最大排列(如降序数组),就重排为最小排列(升序)并返回 false。很多人误以为 false 表示出错或越界,其实只是“没下一个了”。
常见错误现象:next_permutation 调用一次就停,漏掉第一个排列(初始状态本身就是一个有效排列)。
- 必须先对容器排序(升序),否则只能遍历从该初始状态出发的字典序子集
- 想枚举全部排列,得先调用
sort,再用do-while包裹第一次调用 -
next_permutation只接受随机访问迭代器,std::list不行,std::vector和原生数组可以
std::vector<int> v = {1, 2, 3};
std::sort(v.begin(), v.end()); // 必须有
do {
// 处理 v 当前排列
} while (std::next_permutation(v.begin(), v.end()));求组合数 C(n,k) 别直接算阶乘
阶乘爆炸快,int 在 n > 13 就溢出,long long 也撑不过 n ≈ 20。而且组合数本身可能远小于中间阶乘结果,纯属白算。
正确做法是用递推公式:C(n,k) = C(n,k−1) × (n−k+1) / k,边乘边除,避免累积大数。
立即学习“C++免费学习笔记(深入)”;
- 顺序很重要:先乘后除,且每步都保证整除(数学上成立,但顺序错会截断小数)
- k 超过 n/2 时,用 C(n,k) = C(n,n−k) 减少循环次数
- 若只需模意义下结果(比如 1e9+7),除法要转成乘逆元,不能直接用
/
long long comb(int n, int k) {
if (k > n || k < 0) return 0;
k = std::min(k, n - k);
long long res = 1;
for (int i = 1; i <= k; ++i) {
res = res * (n - k + i) / i; // 整除安全
}
return res;
}next_permutation 对重复元素怎么处理
它按字典序跳过重复排列,结果中不会出现两个完全相同的排列——前提是输入已排序。例如 {1,1,2} 排序后调用,只会生成 3 个不同排列:{1,1,2}、{1,2,1}、{2,1,1}。
但如果你传入未排序的重复数组(如 {1,2,1}),next_permutation 仍会工作,只是起点不标准,容易漏或重。
- 重复元素下,
next_permutation的行为是定义良好的,但“全部枚举”必须以升序为起点 - 不需要额外去重逻辑,库函数内部已处理
- 若需所有子集的排列(比如从 5 个数里选 3 个再排列),得嵌套:先用
std::combinations(没有!)手动选,再对每个子集调用next_permutation
性能和替代方案:什么情况不该用 next_permutation
它时间复杂度是 O(n),单次调用很快,但枚举全部排列是 O(n! × n),n=10 就要千万级操作,n=12 直接卡住。实际项目中,多数需求其实只需要部分排列、带剪枝的搜索,或根本不需要显式列出。
- 做回溯题(如 N 皇后、数独)时,手写递归 + 剪枝比生成全排列再验证快得多
- 需要第 k 个字典序排列?用康托展开,O(n²) 定位,不用枚举前面 k−1 个
- 编译器优化对
next_permutation有限,大量调用时注意别让它在 hot path 上
真正要用它的时候,往往是你明确知道 n ≤ 8,或者只跑测试用例——生产代码里硬写全排列枚举,基本等于提前埋雷。










