LeetCode 31. 下一个排列

116 阅读4分钟

题目要求

给定一个整数数组 nums,找到它的下一个排列,即字典序中下一个比它更大的排列。如果不存在下一个排列,则将 nums 重新排列为字典序最小的排列(即升序排列)。

输入输出格式:

  • 输入:一个整数数组 nums
  • 输出:没有返回值,结果直接修改输入数组 nums 以表示下一排列。

理解题意:

下一排列是指在所有可能的排列中,找到一个比当前排列稍大但又是最小的排列。 例如,给定 [1, 2, 3],下一个排列是 [1, 3, 2];而给定 [3, 2, 1],下一个排列则是 [1, 2, 3]

可能涉及的算法知识点:

  • 数组与索引操作
  • 组合数学
  • 逆序查找
  • 交换和反转操作

解题思路:

这题的代码不是难点,难点在于思路的理解。 首先我会想,对于 int[] nums = {1, 2, 3, 6, 5, 4}; 这个数组,肉眼可见的,交换6和3的位置,当然是可以做到:新的数组 126354 是 大于 123654的 ,但是他是不是他的下一个排列呢?不是。因为只是做了第一个步骤,也就是从后往前找到了第一个 nums[i] < nums[i+1] 的。

这里就有个问题了,为什么要从后往前搜索呢?

是有点儿神奇,为什么人脑得出来的结果,是首先交换6和3呢?而不是交换2和1

其实是人脑默认已经知道了,应该“优先交换后面的元素” 这个道理了,人脑是知道前面的位数是比较大的。

所以这里默认就是从后往前搜索,找到第一个可以交互的元素。

接着呢?我们又碰到了一个问题,就是126354其实并不是 123654的下一个排列,明显的,我们是知道最终答案的,也就是这个答案应该是124356,而不是126354,所以其实我们应该尽量把比较小的元素,也就是4往前提

怎么找到4呢?再次从右往左遍历一次,找到了第一个比nums[i] 大的元素,这下我们就可以找到4了。

那现在我们有了两个index,一个是6对应的,一个是4对应的

明显的,我们在找到4之后,应该把4和3交换位置,遵循我们尽量把“小”的元素往前提的原则

交换4 和 3 之后,数组变成了 124653,其实这里仍然不是下一个,肉眼可见的 其实124356 才是。

那怎么把 124653 变成 其实124356 ?

其实就来到了解法的最后一步:反转 i+1到n-1 的元素,也就是把653反转成356.

至此思路已经完全对应上:总共有4步

  • 1、从右往左找到第一个可以交换的元素的index(当然这里如果不存在的话,直接反转就好了)
  • 2、从右往左找到第一个比nums[index]大的元素
  • 3、交换它们
  • 4、反转后面的元素

没什么技巧,纯碎死记硬背。代码实现时,建议也用123654这个例子来实践,可以加深记忆

  1. 找到下一个排列:

    • 从后向前遍历数组,找到第一个逆序的元素 nums[i],即 nums[i] < nums[i + 1]
    • 如果不存在这样的元素,说明当前排列是最大排列,直接反转整个数组。
    • 从后向前找第一个比 nums[i] 大的元素 nums[j],并交换它们。
    • 最后,反转下标从 i + 1 到数组结束的部分。
  2. 时间复杂度与空间复杂度

    • 时间复杂度:O(n),其中 n 是数组的长度,因为我们需要遍历数组几次。
    • 空间复杂度:O(1),在原数组上进行操作,不需要额外的空间。

代码示例(Java):

public class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        // 1. 从后向前找到第一个逆序对
        int i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // 2. 如果找到了逆序对
        if (i >= 0) {
            // 3. 从后向前找到第一个比 nums[i] 大的元素
            int j = n - 1;
            while (nums[j] <= nums[i]) {
                j--;
            }
            // 4. 交换 nums[i] 和 nums[j]
            swap(nums, i, j);
        }
      
        // 5. 反转 i + 1 到 n - 1 的部分
        reverse(nums, i + 1, n - 1);
    }

    // 交换两个元素
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    // 反转数组中的一部分
    private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            swap(nums, start, end);
            start++;
            end--;
        }
    }
}