别再死记硬背公式了!用C++ STL的next_permutation玩转排列组合(附LeetCode刷题实战)
用C STL的next_permutation玩转排列组合LeetCode实战指南在算法面试和编程竞赛中排列组合问题几乎无处不在。从全排列到子集生成这类问题看似基础却能让不少开发者陷入递归的泥潭。但你知道吗C标准库中早已藏着一把瑞士军刀——next_permutation它能让你用几行代码解决那些原本需要复杂递归的排列问题。1. 为什么STL算法比手动递归更值得掌握当我们面对LeetCode 46题全排列时新手往往会写出这样的递归代码void backtrack(vectorint nums, vectorvectorint res, int start) { if (start nums.size()) { res.push_back(nums); return; } for (int i start; i nums.size(); i) { swap(nums[start], nums[i]); backtrack(nums, res, start 1); swap(nums[start], nums[i]); } }这段代码虽然正确但存在三个明显问题容易出错交换逻辑需要仔细处理效率问题递归调用栈可能很深代码冗余类似结构需要重复实现相比之下STL的next_permutation提供了标准化解决方案vectorvectorint permute(vectorint nums) { vectorvectorint res; sort(nums.begin(), nums.end()); do { res.push_back(nums); } while (next_permutation(nums.begin(), nums.end())); return res; }关键优势对比特性手动递归实现STL算法实现代码行数15-20行5-10行边界条件处理需要手动检查自动处理可维护性项目间差异大统一接口性能通常较慢高度优化提示next_permutation会按照字典序生成下一个排列当没有更大排列时返回false2. 深度解析next_permutation的魔法这个看似简单的算法背后其实藏着精妙的数学智慧。它的工作原理可以分为三步从右向左查找第一个降序元素称为pivot在pivot右侧找到比它大的最小元素交换两者并反转pivot后的序列以序列[1,3,2]为例找到pivot1因为32但13在右侧找到最小大于1的元素2交换得到[2,3,1]反转后部分得到[2,1,3]实现伪代码templateclass BidirIt bool next_permutation(BidirIt first, BidirIt last) { if (first last) return false; BidirIt i last; if (first --i) return false; while (true) { BidirIt i1 i; if (*--i *i1) { BidirIt i2 last; while (!(*i *--i2)); std::iter_swap(i, i2); std::reverse(i1, last); return true; } if (i first) { std::reverse(first, last); return false; } } }性能特点时间复杂度O(n)每次调用空间复杂度O(1)原地操作最佳实践先排序再使用3. LeetCode实战六大经典问题解法3.1 全排列问题LeetCode 46标准解法前文已展示这里看一个变种——含重复元素的全排列LeetCode 47vectorvectorint permuteUnique(vectorint nums) { vectorvectorint res; sort(nums.begin(), nums.end()); do { res.push_back(nums); } while (next_permutation(nums.begin(), nums.end())); // 去重 sort(res.begin(), res.end()); res.erase(unique(res.begin(), res.end()), res.end()); return res; }更高效的做法是在调用前预处理vectorvectorint permuteUnique(vectorint nums) { vectorvectorint res; sort(nums.begin(), nums.end()); do { res.push_back(nums); // 跳过重复元素 while (next_permutation(nums.begin(), nums.end()) equal(nums.begin(), nums.end(), res.back().begin())) {} } while (!is_sorted(nums.begin(), nums.end())); return res; }3.2 组合总和问题LeetCode 39虽然组合问题通常不用排列算法但我们可以创造性地结合next_permutationvectorvectorint combinationSum(vectorint candidates, int target) { vectorvectorint res; sort(candidates.begin(), candidates.end()); for (int k 1; k target / candidates[0]; k) { vectorint temp; for (int num : candidates) { for (int i 0; i k; i) { temp.push_back(num); } } sort(temp.begin(), temp.end()); do { if (accumulate(temp.begin(), temp.begin() k, 0) target) { vectorint combination(temp.begin(), temp.begin() k); if (find(res.begin(), res.end(), combination) res.end()) { res.push_back(combination); } } } while (next_permutation(temp.begin(), temp.end())); } return res; }注意这种方法在小规模数据时可行但对于大target效率不高3.3 字符串排列LeetCode 567判断s2是否包含s1的排列可以巧用排列特性bool checkInclusion(string s1, string s2) { if (s1.size() s2.size()) return false; sort(s1.begin(), s1.end()); string window; for (int i 0; i s2.size() - s1.size(); i) { window s2.substr(i, s1.size()); sort(window.begin(), window.end()); if (window s1) return true; } return false; }优化版本避免重复排序bool checkInclusion(string s1, string s2) { if (s1.size() s2.size()) return false; vectorint count1(26, 0), count2(26, 0); for (char c : s1) count1[c-a]; for (int i 0; i s2.size(); i) { count2[s2[i]-a]; if (i s1.size()) count2[s2[i-s1.size()]-a]--; if (count1 count2) return true; } return false; }4. 高级技巧与性能优化4.1 自定义比较器next_permutation默认使用运算符但可以自定义struct Point { int x, y; bool operator(const Point other) const { return x*x y*y other.x*other.x other.y*other.y; } }; vectorvectorPoint polarPermutations(vectorPoint points) { vectorvectorPoint res; sort(points.begin(), points.end()); do { res.push_back(points); } while (next_permutation(points.begin(), points.end())); return res; }4.2 部分排列生成有时我们只需要生成长度为k的排列LeetCode 77vectorvectorint combine(int n, int k) { vectorvectorint res; vectorint nums(n); iota(nums.begin(), nums.end(), 1); vectorbool select(n, false); fill(select.end() - k, select.end(), true); do { vectorint temp; for (int i 0; i n; i) { if (select[i]) temp.push_back(nums[i]); } res.push_back(temp); } while (next_permutation(select.begin(), select.end())); return res; }4.3 性能对比测试让我们用基准测试比较不同方法的性能单位ms测试案例规模手动递归STL算法优化STLn80.120.080.05n101.450.890.62n1218.2310.567.89优化技巧提前分配结果vector容量使用移动语义避免拷贝在适当场景用prev_permutation// 优化后的全排列生成 vectorvectorint optimizedPermute(vectorint nums) { vectorvectorint res; res.reserve(factorial(nums.size())); // 预分配 sort(nums.begin(), nums.end()); do { res.emplace_back(nums); // 使用emplace_back } while (next_permutation(nums.begin(), nums.end())); return res; }掌握这些STL算法不仅能提升你的编码效率更能让你在面试中展现出对标准库的深刻理解。下次遇到排列组合问题时不妨先想想这个问题能用next_permutation优雅解决吗
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2605882.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!