下一个排列
题目类型: 数组
题目来源: leetcode传送门
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列(即,组合出下一个更大的整数)。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例3:
输入:nums = [1,1,5]
输出:[1,5,1]
示例4:
输入:nums = [1]
输出:[1]
提示
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 100
这题开始的想法是O(N2)的,从右侧遍历,假设当前遍历到了ai,那么如果在ai之前有一个比ai小的数字,则将这两个数字交换,然后再对i之后的进行升序排列。要满足的要求就是,被换的数字要尽可能靠右,这样才能保证是比当前字典序刚好大的字典序。这显然是个O(N2)的做法,但是这太暴力了,后来看了题解的方法,实在是太秒了,大概思路一样,但是优化了找到要交换的两个数字以及后面排序的方法,解法如下:
首先考虑一个问题,要是让被交换的数字尽可能靠右,我们应该怎么做,首先必须是从右侧遍历,这是毫无疑问的,那么下一步就是考虑如何用更少的次数去找到这两个数字。假设我们现在遍历到了ai,那么看ai-1的值,显然有两种情况,第一种:ai-1<ai,第二种:ai-1>=ai,对于第一种情况我们可以直接确定ai-1的后面一定有一个比ai-1大的数字,所以这时直接在后面找就可以了,这时循环也可以结束了,所以时间复杂度是O(N)的,对于第二种情况,由于再之前没出现过前一个数比后一个小的情况,所以在ai-1到最后是一个递减的序列,那么就没有必要在这里浪费时间,直接去判断ai-2和ai-1的关系即可。这样做的话,每个数字最多遍历两次,即找到一个ai-1<ai一次,再在ai-1之后找到最后的比ai-1大的数字一次,一共两次,到目前为止时间复杂度是O(N)。下一步就是对ai-1后面的序列进行一个升序排序,但是这个排序直接O(N)就可以完成,下面是原因以及做法。由于之前的信息可得到(假设ai-1<ai,aj是ai-1后面最右的比ai-1大 数字),有如下关系:ai-1<ai>=…>aj>=…且ai-1>=aj+1且aj-1>=aj>=ai-1,所以ai>=ai+1>=…>=aj-1>=ai-1>=aj+1>=…,所以在ai-1和aj交换后,后面是一个降序序列,只需要将后面的反转(reverse)就是升序序列了,所以在O(N)内就可以完成了,并且空间复杂度为O(1)reverse不会的可以看下面的相关代码
代码如下:
void reverse(vector<int>& nums, int begin, int end)
{
int temp;
while(begin < end)
{
temp = nums[end];
nums[end] = nums[begin];
nums[begin] = temp;
begin ++;
end --;
}
}
void nextPermutation(vector<int>& nums) {
int temp;
int left = -1;
int right = -1;
for(int i = nums.size() - 1; i >= 1; i --)
{
if(nums[i] > nums[i-1])
{
left = i-1;
break;
}
}
if(left != -1)
{
int temp = nums[left];
for(int i = nums.size() - 1; i > left; i --)
{
if(nums[i] > temp)
{
right = i;
break;
}
}
nums[left] = nums[right];
nums[right] = temp;
reverse(nums, left + 1, nums.size() - 1);
}
else
{
reverse(nums, 0, nums.size() - 1);
}
}