leetcode-下一个排列

298 阅读4分钟

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

又到了周五,每次到了周五都会感叹一下时光飞逝,1周过去,每天都很忙,但是1周过去好像成果也不明显。

昨天和今天连续2天都没锻炼,立下的flag快到倒了,接下来这个周末的时间更要抓紧了,还有好多计划的事情还没完成,比如想要看的书,关于沟通、关于自动驾驶,还有下周想做的1个关于可转债的分享,还有很多很多类似的事情。周末的时间又特别短暂,希望本周能把这些事情都完成吧。

不过还好,每日做leetcode和更文的习惯还没断,特别是昨天回家都11点多了,最后还是赶在12点之前完成了。今天继续,做leetcode的第31题。

题目

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

示例 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. 从右往左,找到第1个比右边数字小的数,下标为high
  2. 从右往左,找到第1个比num[high]大的数,下标为low
  3. 交换num[high]和num[low]
  4. 反转num[high+1]~num[len-1] 当然,特殊情况要先处理掉,如果第1步找不到high,证明真个数组单调递减,已经是这些数组能表示的最大数值的的,要轮回到最小,直接反转即可。

为什么可以这样做呢?
我们可以这样想,我们要找到一个比当前数字值更大的数,必须是跟当前数字比,更高位有更大的数。在满足更大的前提后,这个数值还要尽量的小,所以这个数值最大的更高位就要尽量靠右。我们可以假设存在下标high,high右边是一个单调递减的序列,那么high右边的数字无论怎么交换,都只会更小,不会更大,所以,必须交换high。那用哪一位交换呢,肯定是要比num[high]更大,同时,要尽量靠右,为什么?因为high右边是1个单调递减的序列,越靠右的数值越小。这回我们明白了如果找到跟high交换的low,但是第4步又是为什么呢?这是因为,high这1位交换成1个更大的数值后,无论后面这么排列,都会比原来的数值更大了,为了获取最小值,我们应该把high右边的数值从小到大排列,我们直到,交换之前,high右边是个单调递减的序列,num[low]又是右边第1个比num[high]大的数值,所以交换后,high的右边还是一个单调递减的序列,想要实现从小到大排列,刚好只要反转即可。

Java版本代码

class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        // 定义高位和低位的下标
        int high = -1, low = -1;
        for (int i = len - 1; i > 0; i--) {
            if (nums[i-1] < nums[i]) {
                high = i - 1;
                break;
            }
        }
        if (high == -1) {
            // 证明未找到比后一个数字小的数字下标,整个数组为降序,下一个排列为全部反转成最初始的升序
            int left = 0, right = len - 1;
            while (left < right) {
                swap(nums, left, right);
                left++;
                right--;
            }
            return;
        }
        // 找到了high,去找能跟它交换的low,这个low要尽量靠右
        for (int i = len - 1; i > high; i--) {
            if (nums[i] > nums[high]) {
                low = i;
                break;
            }
        }
        // 交换high和low,交换后反转high后面的数组
        swap(nums, high, low);
        int left = high + 1, right = len - 1;
        while (left < right) {
            swap(nums, left, right);
            left++;
            right--;
        }
        return;
    }

    /**
     * 交换数组中的2个值
     * @param nums
     * @param a
     * @param b
     */
    private static void swap(int[] nums, int a, int b) {
        nums[a] += nums[b];
        nums[b] = nums[a] - nums[b];
        nums[a] -= nums[b];
    }
}