[leetcode] 31. 下一个排列

题目描述

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

解题方法

两遍扫描

题目说了原地 修改,只允许使用额外常数空间。那就不能使用额外空间按大小存储数字,只能使用常数空间的变量。

我们需要做的是,找到一种方法,使新排列字典序大于旧排列,但是这个排列变大的幅度要是最小的。具体怎么做呢?我下面简单说一下。

  • 第一步,我们先找到一对最靠近右边较小数较大数较小数较大数左边,较小数较大数不一定相连,找到的较大数是最接近较小数的数。找到之后,我们需要交换两个数的位置。
  • 第二步,我们记录较小数原来的下标位置,交换之后现在这个位置是较大数,这个位置之后的数字都是按照非严格递减排序的,我们需要将后面的数字翻转过来,按照非严格递增排序。还有一种可能是没有找到较小数较大数,原排列本身就是非严格递减的,此时我们直接将原排列翻转过来。

以排列 [ 3 , 6 , 2 [3,6,\color {red} 2 [3,6,2, 3 , 3 3,\color {red}3 3,3, 1 ] 1] 1] 为例:

  • 第一步,我们找到的较小数2,较大数是最靠近右边3,此时,将两个数交换后,原排列变为 [ 3 , 6 , 3 [3,6,\color {red} 3 [3,6,3, 3 , 2 , 1 ] 3, 2, 1] 3,2,1]
  • 第二步,我们将交换后的较大数后面的数字翻转过来,此时原排列变为 [ 3 , 6 , 3 [3,6,\color {red} 3 [3,6,3, 1 , 2 , 3 \color {green} 1, 2, 3 1,2,3 ] ] ]。此排列就是原始整数数组的下一个排列

我描述一下该方法:

  • 第一步,设数组为 a a a,长度为 n n n。我们从后向前找到数组中第一个顺序对 ( i , i + 1 ) (i, i+1) (i,i+1),使得 a [ i ] < a [ i + 1 ] a[i] < a[i+1] a[i]<a[i+1]。此时 [ i + 1 , n ) [i+1, n) [i+1,n)非严格递减排序。 a [ i ] a[i] a[i]即为较小数
  • 第二步,我们从 [ i + 1 , n ) [i+1, n) [i+1,n)中找到最接近 a [ i ] a[i] a[i]较大数 a [ j ] a[j] a[j],并使 a [ j ] a[j] a[j] 尽可能靠右
  • 第三步,此时 [ i + 1 , n ) [i+1, n) [i+1,n)非严格递减排序,我们直接使用双指针翻转区间 [ i + 1 , n ) [i+1, n) [i+1,n),使其变为非严格递增排序。如果没有找到较小数较大数,我们直接将区间 [ 0 , n ) [0, n) [0,n)进行翻转

java代码

public void nextPermutation(int[] nums) {
    if (nums == null || nums.length < 2) {
        return;
    }
    // 记录反转数组的起始位置
    int index = -1;
    for (int i = nums.length - 2; i >= 0; i--) {
        // 当左边的数比右边的数小时
        if (nums[i] < nums[i + 1]) {
            // 交换的数是最后两位
            if (i == nums.length - 2) {
                swap(i, i + 1, nums);
                return;
            } else {
                // 比nums[i]大且最接近nums[i]的数的下标
                int minIndex = i + 1;
                // nums[i + 1] ~ nums[nums.length - 1]是非严格从大到小排列
                for (int j = i + 2; j < nums.length; j++) {
                    // 从 nums[i + 1] ~ nums[nums.length - 1]中寻找比nums[i]大且最接近nums[i]的数
                    if (nums[i] < nums[j]) {
                        minIndex = j;
                    } else {
                        break;
                    }
                }
                swap(i, minIndex, nums);
                // 记录反转数组的起始位置
                index = i + 1;
                break;
            }
        }
    }

    int l;
    int r = nums.length - 1;
    if (index != -1) {
        l = index;
    } else {
        l = 0;
    }
    //从数组下标 l ~ r 进行数组交换
    while (l < r) {
        swap(l, r, nums);
        l++;
        r--;
    }
}

// 数组下标i和j交换函数
public void swap(int i, int j, int[] nums) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

复杂度分析

时间复杂度: O ( N ) O(N) O(N),设数组长度为 N N N,需要扫描两次数组。
空间复杂度: O ( 1 ) O(1) O(1),只需要存储常数级别的指针和变量。


  • 个人公众号
    个人公众号
  • 个人小游戏
    个人小游戏
  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的大鱼人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值