力扣31和1053题目:下一个排列和上一个排列

下一个排列算法
题目描述:给你一个整数数组 nums ,找出 nums 的下一个排列。

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列。
例如arr=[1,2,3],它的下一个排列为[1,3,2].
例如arr=[3,2,1],它是最大的排列,因此不存在

假设一个序列是升序序列:arr=[1,2,3,4],那么它是最小所有排列序列中最小的。
如果一个序列是降序序列,arr=[4,3,2,1],那么它是最大的序列,所以没有比之更小的了。
此外,由于寻找的是大于某个序列值的最小序列,所以尽可能的从右往左调整,例如:143652,如果调整左侧的值将4和6交换得到的值为[163452]一定大于交换3和6的值[146352]。
假设给定的数组arr=[1,4,8,6,5,2]。如下图所示:可以看出来从高到低的变化点在下标为2的位置处。
在这里插入图片描述

具体的思路如下:
1.从右向左找到第一个升序的子序列的位置,由于a[4]>a[5]跳过,a[3]>a[4]跳过,a[2]>a[3]跳过,a[1]<a[2],故该位置是下标为1的位置,对应元素为4.
2.从该位置开始,从右向左依次向遍历,如果遍历的数组元素大于升序位置处的元素,那么就交换两个元素的位置。交换之后的元素为[158642]。
需要注意的是,如果升序位置之后的序列小于当前位置的序列值,例如这里的2小于4,是不需要发生交换的。
3.将升序位置后的所有元素进行反转,反转之后的元素为[152468]
代码如下所示:
实现的方法有多种,自己习惯于使用for循环,满足条件退出这种的写法,其中left代表的是升序序列的位置,而right代表的是比arr[left]大的最小的下标位置。之后交换即可。

class Solution {
public:
    void swap(int& left,int &right)
    {
        int tmp=right;
        right=left;
        left=tmp;
    }
    //采用指针的写法,注意这里的tmp是临时变量不能使用指针哈!
    void swap(int *left,int *right)
    {
        int tmp=*right;
        *right=*left;
        *left=tmp;
    }
    void reverse(vector<int>& nums,int left,int right)
    {
        while(left<=right)
        {
            swap(&nums[left],&nums[right]);
            left++;
            right--;
        }
    }
    void nextPermutation(vector<int>& nums) {
        int i=nums.size()-2;
        int left=-1;
        for(int i=nums.size()-2;i>=0;i--)
        {
            if(nums[i]<nums[i+1])
            {
                left=i;
                break;
            }
        }
        if(left==-1)
        {
             reverse(nums,0,nums.size()-1);
             return;
        }
        int right=nums.size()-1;
        //注意这里是用来寻找right的,如果后面的数小于前面的数,是不能交换的。例如58432,如果交换5和2的话,结果会变得更小。
        while(right>=(left+1)&&nums[right]<=nums[left])
            right--;
        //交换两个数
        swap(nums[left],nums[right]);
        reverse(nums,left+1,nums.size()-1);
        
    }
};

上一个排列算法
同下一个排列算法很相似,该算法是用来寻找当前排列组合的上一个排列组合。
例如给定arr=[1,9,4,6,7],那么上一个排列组合就是[1,7,9,6,4]。
思路是类似的:由于需要的是小于当前序列的组合,因此如果序列是升序排列的话,它已经是最小的,所以需要寻找降序的下标位置。
由于这里仅仅交换了,如果需要寻找最大值的话,交换之后再逆序输出降序位置之后的元素就可以了。
需要注意以下这个点:
假设给定的arr=[3,1,1,3],如果没有考虑降序下标之后的元素相等的情况,就会有以下问题。因此如果元素相等的时候,需要向左继续遍历,需要添加这行代码哦!

 while(j-1>i&&arr[j]==arr[j-1])
                j--;

在这里插入图片描述
贴一个代码:

class Solution {
public:
    vector<int> prevPermOpt1(vector<int>& arr) {
        int i=arr.size()-2;
        //i的取值从倒数第二个位置开始,如果是升序的话,直接跳过。
        while(i>=0&&arr[i]<=arr[i+1])
            i--;
        if(i<0)
            return arr;
        if(i>=0)
        {
            int j=arr.size()-1;
            while(j>i&&arr[i]<=arr[j])
                j--;
            //j-1的取值大于等于i+1
            //必须要加,否则报错。
            while(j-1>i&&arr[j]==arr[j-1])
                j--;
            swap(arr[i],arr[j]);
        }
        return arr;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值