Next Permutation
原题链接Next Permutation
重新实现next_permulation函数
template <class BidirectionalIterator>
bool next_permutation (BidirectionalIterator first,
BidirectionalIterator last);
next_permulation,接受两个迭代器,表示区间[first, last],将这个区间排列成下一个较大的序列,如果当前区间已经是最大的序列(降序),则返回false。
next_permulation直接在原区间上更改,比如
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
int main()
{
using namespace std;
vector<int> nums{1, 2, 3, 4};
do
{
copy(nums.begin(), nums.end(), ostream_iterator<int>(cout, " "));
cout << endl;
}while(next_permutation(nums.begin(), nums.end()));
return 0;
}
输出结果
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
...
4 2 3 1
4 3 1 2
4 3 2 1
注意next_permutation
每次只能找比当前序列大的下一个序列,如果没有就返回false
。并不是打印所有的排序。如果nums
初始化为{2, 1, 3, 4}
,那么就只会从{2, 1, 3, 4}
这个序列开始找,不会有{1, *, *, *}
这样的排列
题目要求就是实现next_permutation函数,返回当前序列的下一个序列(排列),要求比当前序列大,但是是所有可能的结果中最小的,比如说
寻找1 3 2的下一个排序
返回的应该是2 1 3而不是2 3 1,因为2 1 3是所有可能中最小的
这也是next_permutation的规则,每次只找比当前排列大的所有可能的结果中最小的那个排列
既然要变大,又要让变大的幅度尽量小,就需要尽量让前面的数字保持不变,只改变后面几个的顺序。拿数字来说就是尽量只改变个位十位百位再往上的顺序,尽量不改变万位,十万位,百万位那些高位的顺序,这样才能让变大的幅度小一些。所以很显然要从后往前遍历。
思路如下,假设nums大小为n
- 从后向前遍历,i记录当前遍历到的位置
- 因为只有当递减时才没有更大的排序,所以[i + 1 : n)一定是递减的
- 如果[i + 1 : n)中有比nums[i]大的元素,找到最后一个比nums]i]大的元素(因为递减),与nums[i]互换位置
- 此时nums[i]已经比原先的nums[i]大,高位变大,低位应该尽量变得最小
- 将[i + 1 : n)逆序,因为以前递减,互换后仍然递减,为了变小,应该让后面序列递增
代码如下
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
/* 从倒数第二个开始,判断后面有没有比nums[i]大的数 */
for(int i = n - 2; i >= 0; --i)
{
/* 因为[i+1: n)是递减的,如果大于最大的那个,就说明不存在比nums[i]大的 */
if(nums[i] >= nums[i + 1])
continue;
/* 二分查找找到最后一个比nums[i]大的位置 */
int j = binary_find(i + 1, n - 1, nums, nums[i]);
/* 交换位置,让高位变大 */
swap(nums[i], nums[j]);
/* 逆序,高位变大后,让低位变得最小 */
reverse(nums.begin() + i + 1, nums.end());
return;
}
/* 如果整个nums最开始就是递减的,那么没有更大的排序,变成最小的 */
reverse(nums.begin(), nums.end());
}
private:
/* 二分查找,找到最后一个大于n的位置 */
int binary_find(int left, int right, vector<int>& nums, int n)
{
/*
* left左边一定都大于n,right右边一定都小于等于n。返回后left > right
* 所以nums[right] > n, nums[left] <= n
* 所以nums[right]就是最后一个大于n的数
*/
while(left <= right)
{
int middle = (left + right) / 2;
/* 防止无限循环,所有不管大于/小于都改变其中一个大小 */
/* 如果中间位置大于n,说明在右边,但是middle有可能也是最后的结果 */
if(nums[middle] > n)
left = middle + 1;
/* 如果小于等于n,说明在左边 */
else
right = middle - 1;
}
return right;
}
};