LeetCode题练习与总结:下一个排列

158 篇文章 0 订阅
95 篇文章 0 订阅

一、题目描述

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

  • 例如,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

二、解题思路

  1. 从数组的末尾开始向前遍历,找到第一个递减的子序列的起始点。这个点就是我们需要进行交换的元素之一。
  2. 在找到的递减子序列之后,找到比当前元素大的最小元素。这个元素将成为交换的第二个元素。
  3. 交换这两个元素。
  4. 反转递减子序列之后的子数组,以确保它是递增的,这是为了确保新的排列是字典序更大的排列。

三、具体代码

class Solution {
    public void nextPermutation(int[] nums) {
        if (nums == null || nums.length <= 1) {
            return; // 如果数组为空或只有一个元素,不需要改变
        }

        // 1. 从后向前找到第一个递减的子序列的起始点
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // 如果i为-1,说明数组已经是递增的,需要反转整个数组
        if (i == -1) {
            reverse(nums, 0, nums.length - 1);
        } else {
            // 2. 在递减子序列之后找到比当前元素大的最小元素
            int j = nums.length - 1;
            while (nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j); // 交换这两个元素

            // 3. 反转递减子序列之后的子数组
            reverse(nums, i + 1, nums.length - 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--;
        }
    }
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 在最坏的情况下,我们需要遍历整个数组来找到第一个递减的子序列的起始点,这需要 O(n) 时间,其中 n 是数组的长度。
  • 接下来,我们可能需要再次遍历数组来找到比当前元素大的最小元素,这同样需要 O(n) 时间。
  • 最后,我们可能需要反转数组的一部分,这部分的时间复杂度是 O(n),但因为这部分只涉及数组的后半部分,所以实际上这部分的时间复杂度是 O(n/2)。
  • 综合考虑,最坏情况下的总时间复杂度是 O(n) + O(n) + O(n/2) = O(n)。
  • 这是因为在实际应用中,我们通常不会执行所有的操作,而且数组的反转操作不会每次都发生。
  • 因此,我们可以认为这段代码的平均时间复杂度是 O(n)。
2. 空间复杂度
  • 这段代码使用了额外的常数空间来存储临时变量(例如,在 swap 方法中交换元素时使用的 temp 变量)。
  • swapreverse 方法都是原地操作,没有使用额外的数组或数据结构。
  • 因此,空间复杂度是 O(1),即常数空间复杂度。

五、总结知识点

  1. 数组操作:代码处理的是一个整数数组,涉及到数组的遍历、元素交换和反转等基本操作。

  2. 原地算法:整个 nextPermutation 方法是在原数组上进行操作的,没有使用额外的数组来存储中间结果。这是算法设计中的一个重要概念,可以节省空间。

  3. 递增和递减序列:代码中判断数组是否已经是递增序列,这是通过比较相邻元素的大小来实现的。如果数组已经是递增的,那么它的下一个排列就是递减序列。

  4. 交换操作swap 方法用于交换数组中的两个元素。这是原地排序和排列操作中常用的技巧。

  5. 反转数组reverse 方法用于反转数组的一部分。在找到下一个排列的过程中,如果需要将递增序列转换为递减序列,就需要反转数组的一部分。

  6. 循环控制:代码中使用了 while 循环来控制遍历数组的过程,这是处理序列问题时常见的控制结构。

  7. 条件判断:代码中使用了 if 语句来进行条件判断,例如判断数组是否需要反转,以及寻找合适的元素进行交换。

  8. 递归思想:虽然这段代码没有直接使用递归,但是理解递归对于解决排列和组合问题很有帮助。在某些情况下,可以通过递归的方式来生成所有可能的排列。

  9. 边界条件处理:在处理数组时,需要考虑边界条件,例如数组为空或只有一个元素的情况,以及在反转数组时的起始和结束索引。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

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

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

打赏作者

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

抵扣说明:

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

余额充值