题目描述:
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
方法一:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
int i;
for (i = n - 2; i >= 0; i--)
{
int j = i + 1;
int minx = INT_MAX;
int min_maxnum = INT_MIN; //记录所有大于nums[i]的值中的最小值
int index = -1; //min_maxnum的下标
while (j < n)
{
if (nums[j] > nums[i] && nums[j] - nums[i] < minx)
//寻找排在nums[i]的后面的数字中,大于nums[i]且两者差距最小的值
{
minx = nums[j] - nums[i];
min_maxnum = nums[j];
index = j;
}
j++;
}
if (min_maxnum > nums[i])
{
swap(nums[i], nums[index]); //交换
break;
}
}
sort(nums.begin() + 1 + i, nums.end()); //将后续的数字从小到大排列
}
};
时间复杂度为O(n²),空间复杂度为O(1)。
文字不太好表达,举个例子用于该方法的应用:
样例{1,2,5,3,6,4}:首先i=5,nums[i]=6,nums[i]的右边没有比6还大的数字, 结束;然后i=4,nums[i]=3,nums[i]的右边比3还大的数字有6和4,其中和3差距最小的为4,则min_maxnum=4,index=5,交换3和4,数组变为{1,2,4,6,3},退出循环。最后将4后面的数字排序,数组变为{1,2,4,3,6},即为所求。
另外,在我的vs编译器中,当i=-1时,nums.begin()+i+1会报错,理由是cannot seek vector iterator before begin,只有改成nums.begin()+1+i才能过,而在leetcode的编译器里倒是都可以,反而前者还能够更快达到0ms。
方法二:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
int i = n-2;
while (i >= 0 && nums[i] >= nums[i + 1])
//从右起,找到第一个小于右边元素的数
{
i--;
}
if (i < 0)
//i=-1,说明这个数组是降序序列,不可能还有更大的排列方式了,直接翻转取最小排列
{
reverse(nums.begin(), nums.end());
return;
}
for (int j = n - 1; j > i; j--)
//从右起,寻找第一个比刚才找到的nums[i]大的数将两者交换
{
if (nums[j] > nums[i])
{
swap(nums[i], nums[j]);
break;
}
}
//显然,交换完之后,新的nums[i]的右边为降序序列,将右边翻转为升序序列
reverse(nums.begin() + i + 1, nums.end());
}
};
来自官方的题解,这个方法解题思路和我的方法一是差不多的,都是找到一个位于右边的较大值和一个位于左边的较小值,将两者交换,并把右边的序列变为升序序列。但是官方的这个方法更为巧妙,分别进行了两次扫描,而且没有用到sort()函数,时间复杂度只为O(n),空间复杂度也为O(1)。
注意,reverse()函数的时间复杂度为O(n)。
另外该方法也在在 C++ 的标准库函数 next_permutation
中被采用,在评论中看到一个神奇的解法