下一个排列

题目描述:给定一个若干整数的排列,给出按正数大小进行字典序从小到大排序后的下一个排列。如果没有下一个排列,则输出字典序最小的序列。

样例:左边是原始排列,右边是对应的下一个排列。

1,2,3 → 1,3,2

3,2,1 → 1,2,3

1,1,5 → 1,5,1


先解释一下“字典序”。官方的准确定义我就不说了,自己可以去查。我只说说针对这类编程题,我们应该怎么理解。

一个序列,这里,为了简单,就当做是正整数的序列吧,它有自己的排列方式,比较有规则的是两种:

1. 升序:由小到大,依次排列,如1, 2, 3, 4

2. 降序:由大到小,依次排列,如4, 3, 2, 1

“字典序”中,我们将升序记为第一个排列,而将降序记为最后一个排列,显然,对于上面这个有4个元素的数组来说,一共有4!个排列,也就是说,除了我上面写出来的两个,还有22个排列。这些排列的顺序,就是“字典序”。

字典序的规律是这样的:首先,我先给两个概念:

1. 高位:越靠左越高

2. 低位:越靠右越低

也就是说,一个排列中,如果元素A在元素B的左边,则说A是高位,B是低位。而“字典序”就是一个不断增大高位数值,减小低位数值的过程。比如排列1, 2, 3三个整数,字典序的排列是这样的:

(1) 1, 2, 3

(2) 1, 3, 2

(3) 2, 1, 3

(4) 2, 3, 1

(5) 3, 1, 2

(6) 3, 2, 1

从(1)到(6)就是不断增加高位数值的过程。

那么怎么找下一个排列呢?我们可以这样考虑:


1. 先找到需要改变的高位:从右向左扫描排列,若一直满足nums[i] > nums[i - 1],则说明这些元素是满足高位大于低位的,不需操作,直到找到nums[i] < nums[i - 1],找到高位比低位小的了,而且是“最低”的高位,这个位置就是我们需要做交换操作的。比如:6, 8, 7, 4, 3, 2 当中的8(发现6 < 8)

2. 第二步找要和这个高位交换的低位:原则是尽量寻找只比这个高位“大一点”的低位,因为只是下一个排列。因为在这个高位右边的数组满足从右往左递增,所以,我们重新从右边起扫描,找到第一个比这个高位大的元素。比如:6, 8, 7, 4, 3, 2 中的7.

3. 交换高位与低位,使得高位变大:比如6, 8, 7, 4, 3, 2 -> 7, 8, 6, 4, 3, 2

4. 此时,不论交换后的高位后面的元素是如何排列的,都肯定比之前的排列靠后了。因为只是下一个排列,所以我们现在尽量要现在的这个排列“靠前”,怎么做呢,就是按升序排列高位后面的元素,比如:按升序排列此时7后面的元素。因为此时高位后面的元素一定是从左往右,从大到小,所以,也相当于是翻转这一部分的数组。比如:78, 6, 4, 3, 2 -> 72, 3, 4, 6, 8


整个步骤完成,很多博客只是给了大家方法,对原理的解释实在是有些含糊,我在这里希望给大家说明白了。

代码如下:

class Solution:
    # @param nums: a list of integer
    # @return: return nothing (void), do not return anything, modify nums in-place instead
    def nextPermutation(self, nums):
        n = len(nums)
        if n == 0 or n == 1:
            return
        right_index = n - 1
        pivot = 0
        # 从右边起找第一个(i - 1, i),使得nums[i] > nums[i - 1]
        while right_index >= 0:
            if right_index - 1 >= 0 and nums[right_index] > nums[right_index - 1]:
                # 将找到的i - 1记为pivot
                pivot = right_index - 1
                break
            elif right_index == 0:
                pivot = right_index - 1
                break
            else:
                right_index -= 1
        # 若pivot < 0,则说明这是最后一个排列了,就需要将nums改为最小的排列
        if pivot < 0:
            nums.sort()
        else:
            right_index = n - 1
            # 从右边起,找第一个大于nums[pivot]的数,并将这个数与nums[pivot]交换
            while right_index >= 0:
                if nums[right_index] > nums[pivot]:
                    nums[right_index], nums[pivot] = nums[pivot], nums[right_index]
                    break
                else:
                    right_index -= 1
            # 将此时pivot之后的部分数组翻转(其实也就是重新排序)
            left = pivot + 1
            right = n - 1
            while left < right:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
                right -= 1
        # write your code here


lintcode上还有一道“下一个排列”的问题,只不过元素可重复,其实原理,甚至连代码都跟这道题一样,我就不说了。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值