LeetCode 31 下一个更大字典序排列 97%解法 清晰思路演变分析

题目详情

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。

示例 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,2,3],实际上就是123,我们找刚好比它大的数,就是132。
而115,也是十分明显,答案是151.
很明显,我们这里都涉及到了交换。

依然很明显,将整个序列中靠左的且小的数X和靠右的且大的数Y进行交换,整个序列就会变大。
X的位置越右,整个序列变大的程度就会越小(这是我们所期望的)
Y的值越小,整个序列变大的程度就会越小(这亦是我们所期望的)

所以我们希望找到一对X,Y,它们满足X在左,Y在右,且前者小,后者大。同时X位置尽量靠左,Y大小尽量小。

XY的逻辑还是很好搞的。我们从后往前遍历,每次记录下当前遍历集合的最大值,然后将当前值与最大值进行比较,若当前值<最大值,则找到了X,否则继续往前遍历(当然很有可能找不到,此时序列肯定是形如4321的降序排列,我们按照题意将其reverse一下即可)。
那么Y如何找呢,Y是最大值吗?不一定。eg:1232654。
我们来走一下流程:

5,max为4,不行
到6,max为5,不行
到2,max为6,此时行了。

我将2与6交换,则会得到1236 542。这是不对的。
但是我可以轻松举出反例:1234 652。
问题在于,Y不够小。
所以这里我们需要再次从后往前遍历,找到第一个大于X的值,这个值就是我们要找的真正的Y(这里不加证明的认为Y<=max,这个可以证明,但也很好想,不说了)。

一开始,本题我也是这样做的,但提交之后发现错误。
分析了一会儿发现了错误在哪。该题要求是刚好最大的序列。
就上面的列子,我们可以找到一个更小的答案:1234 256。这里我打了一个空格来区分。明显,这里进行了一个排序。这个确实就是最小的答案了,也就是正确的答案了。

实现也很简单,将X后面的值进行排序就ok。
至于为什么要这么做。
我们之前的交换能够导致整个序列变大,变大一个X->Y的10^i级别的变化(认为i是X从数组尾开始的索引)。但是很明显X之后的数值都是降序的排列,大的数字在高位,小的数字在低位。那么此时,将它们重新升序排序,就会比之前小。
所以交换是让序列变大一个数量级别,排序是让整个序列在该数量级别中达到自己所能达到的最小值。

代码实现

代码实现很简单,其中注释就是我思考的过程。

class Solution {
    
    
    public void nextPermutation(int[] nums) {
    
    
        //排列成字典序中下一个更大的排列
        //首先了解下字典序把
        //如果我理解的没错的话,应该就是交换顺序,使得新的数刚好大一点
        //第一个例子是将2,3交换,因为
        //从第一个元素开始往后,一直找,只要不是降序排列的一定存在某两个元素A,B。A<B
        //那么将这个A与最后一个大值交换即可
        //这个问题也就从后往前,找第一个比后面小的数,然后和最靠后比它大的数进行交换
        //唯一找不到的情况就是,前面只有比后面大的,没有比后面小的
        //从后面开始往前遍历,每次记录下所扫描过的最大值,然后将当前值与最大值进行比较
        //如果小于,则找到,则将二者交换顺序,直接返回
        //如果遍历完都没找到,那么将原数组reverse即可
        //这题考虑的时候少考虑了,它是有重排序的
        //它会把交换的之间进行后移
        //比如132443
        //不是后移,而是进行一个整体的重新排序
        
        if(nums.length==0){
    
    
            return;
        }
        int max=nums[nums.length-1];
        int i;
        for(i=nums.length-2; i>=0; i--){
    
    
            if(nums[i]<max){
    
    
                break;
            }
            else{
    
    
                max = nums[i];
            }
        }
        if(i!=-1){
    
    
            //不是和max交换,而是找到一个大于nums[i]的最后一个数(逆序第一个)
            int j = nums.length-1;
            while(nums[j]<=nums[i]){
    
    
                j--;
            }
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
            //将i+1之后的进行重排序
            //因为不能用O(n)的空间,所以只能牺牲时间复杂度来进行排序了
            //重新排序
            Arrays.sort(nums,i+1,nums.length);
        }
        else{
    
    
            //因为不能用O(n)的空间,所以只能牺牲时间复杂度来进行排序了
            Arrays.sort(nums);
        }
        return;
    }
}

动图

在这里插入图片描述

(出自该题LeetCode官方题解)
代码通过之后看题解,发现题解跟我是一样的想法,哈哈哈好开心,这里搬运一下,也便于我之后回来复习。

猜你喜欢

转载自blog.csdn.net/qq_34687559/article/details/109821654