LeetCode [31] 下一个排列 Python实现

题目

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

思路

一直最简单暴力的思路是利用回溯法求所有全排列顺序后,再通过排序后找到结果,该方法在leetcode中会Time Limit Exceeded,因此在这里我们不做讨论。

我们从leetcode给的样例入手,将该样例中分成不同情况(情况4是我在第一次运行WA以后注意到的 )。
在这里我们定义了一个reverse翻转函数,稍后我会解释该函数的意义。

情况1
1,2,3 → 1,3,2
对于这类情况,我们容易想到利用一个指向尾部的指针(设i = length - 1)依次从后向前比,若满足条件 nums[i] > nums[i-1],也就是 3 > 2 的情况,在这种情况中我们仅需将两个位置互换便可组成序列 [1,3,2]。(实际上会是单纯的只要向前比就找到后一项比前一项大以后,就直接两个元素互换就OK了吗?当然不是,这里我们首先把满足条件的 i 值记录下来,因此break掉)。

情况2
3,2,1 → 1,2,3
若情况1所在的for循环没有break掉,是不是就证明了 nums[i] <= nums[i-1] 恒成立,也就是 [3,2,1] 这种情况,依据题意,我们须将该序列进行从头到尾翻转一遍,即 reverse(nums, 0, length-1),并且return

情况3
5,4,7,6,3,2 → 5,6,2,3,4,7
情况3中我们先看一个例子,如下图所示:
pic 1
这里话题有回到了刚才的情况1,是简单的将数字4和数字7简单的交换一下吗?当然不是,我们通过观察发现,序列[7,6,3,2]是按照降序排列的,因此要组成下一个排列的序列,我们需要从降序序列中去挑选一个 j 满足:nums[j+1] <= nums[i-1] < nums[j],如图绿色部分所示,因此我们将数字6和数字4进行交换(如下图所示),交换后我们发现新组成的序列 [7,4,3,2] 仍然满足降序排序,而我们要得到原序列 [5,4,7,6,3,2] 下一个排列,因此我们需要将序列 [7,4,3,2] 按从小到大排序,但由于满足降序的性质,我们仅需将序列从位置 i 到尾部 length-1 进行翻转就行。(情况1只是一种特例)
pic 2

情况4
1,3,2 → 2,1,3
这里有一个特殊情况,就是整个 for j in range(i, length-1) 循环遍历过程中,nums[i-1] 既小于了 nums[j],又小于nums[j+1],可以看到这里的数字1是小于数字3和数字2的,因此我们就将序列中最后一个元素同位置 i-1 中的元素进行交换,即图中绿色部分的位置 length-1 元素与位置 i 中的元素进行交换。
pic 3
还剩一种1,1,5 → 1,5,1是为了告诉我们序列中存在重复的数字,因此在 nums[j+1] <= nums[i-1] < nums[j] 这里,我们是让 nums[j+1] 要小于等于 nums[i-1]。

综上,我们可以得出的大致算法是从序列的末尾开始向前遍历,首先找到一个 i 满足 nums[i] > nums[i-1],接着从 i ~ length-1 中寻找一个最小的 nums[j] 并且 nums[j] 大于 nums[i],交换后新的 i ~ length-1 序列进行序列翻转即可。

代码

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        # 定义reverse翻转函数
        def reverse(nums, left, right):
            while left < right:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
                right -= 1

        length = len(nums)
        for i in range(length-1, 0, -1):
            # 情况1
            if nums[i] > nums[i-1]:
                break
        else:
            # 情况2
            reverse(nums, 0, length-1)
            return
        for j in range(i, length-1):
            if nums[j+1] <= nums[i-1] < nums[j]:
                # 情况3
                nums[i-1], nums[j] = nums[j], nums[i-1]
                break
        else:
            # 情况4
            nums[i-1], nums[length-1] = nums[length-1], nums[i-1]

        reverse(nums, i, length-1)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值