<algorithm>
库里有一个名不见经传的函数叫作:std::next_permutationios
简单写个用法示例:面试
cpp#include <iostream> #include <array> #include <algorithm> int main() { std::array<int, 4> A = {1,2,3,4}; do { for (auto i : A) std::cout << i << " "; std::cout << std::endl; } while (std::next_permutation(A.begin(), A.end())); }
输出(截取部分):算法
1 2 3 4 // 2->3->4 : ascending 1 2 4 3 1 3 2 4 1 3 4 2 1 4 2 3 1 4 3 2 // 4->3->2 : descending 2 1 3 4 2 1 4 3 2 3 1 4
恐怕你已经发现了,这个方法能够帮助咱们列出某个容器的全套排列组合。它其实还有一个兄弟函数,名曰 std::prev_permutation,一个是向前排列,一个是向后排列,算法上大同小异。函数
探秘code
提到这个,缘由一是这对姐妹藏在深闺无人知,科普一下;更重要的缘由,是我对于其排列的算法很感兴趣,它是按照什么顺序来进行排列组合的?这个 STL 算法函数若是让我来实现,会如何实现?leetcode
实际上,仔细观察上面输出,大致上咱们能够看出一点端倪:get
综上,咱们若要获得下一步的组合,应该将注意力集中在递增的子序列上,设置两个迭代器,start 与 last,分别指向递增子序列的手尾,用此来重点分析一下从第二行到第三行的转变。it
1 2 4 3 ^ ^ s l
这种状况下, s 指向了 2,意味着 1,2 开头的组合已经排列完毕,根据第 1 条,咱们但愿去排列 1,3 开头的组合了。而此刻 3 在行尾,咱们就但愿将其提取到 2 的前头去。即变成:io
1 3 2 4
果真就是我们想要的了。但是这个插入过程实际上应分为两步:ast
解释一下第二步,当 last 指向 4,这已经意味着 4 之后必定是降序的,即便交换了 2,3 也没法改变这个顺序。而咱们所但愿的,显然是保持 2,4 这样的升序,做为 1,3 开头排列的起始。因此才有了第二步的逆序。
动手
讲明算法,再来理一理实现思路。
cpp#include <algorithm> template<class Iterator> bool my_next_permutation(Iterator beg, Iterator end) { if (beg == end) return false; // 容器为空 Iterator start = end; if (beg == --start) return false; // 仅有一个元素 for(;;) { Iterator last = start--; // last 指向最后一个元素,并将 start 前移 if (*start < *last) { // 直至定位到一个升序序列 Iterator riter = end; // 依旧从尾部开始 while (!(*start < *--riter)) // 找到下一个排列的开头,恰是第一个大于当前开头的元素 ; std::iter_swap(start, riter); // 交换 std::reverse(last, end); // 逆序 return true; } if (start == beg) { // 特殊状况,即所有排列完毕(总体降序),回到初始(总体升序) std::reverse(beg, end); return false; } } }
面试
实际上,这道题也是一道面试题,请见:Next Permutation on LeetCode
动手作作吧~