LeetCode 31. Next Permutation 下一个排列(Java)

题目:

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

解答:

本题即为字典序算法。主要分为三步:

  1. 将 nums 从后向前判断,找到 i 使得首次出现 nums[i] > nums[i-1],此时第 i-1 位为下一步即将置换的一位数。(此时的第 i 位向后直至 nums 尾部,均为逆序排列)
  2. 从后向前遍历 nums,找到 nums[j] 为首位刚刚好大于它的数字。(即逆序区域中大于 nums[i],且最接近 nums[i] 的一位数)
  3. 通过 swap(),置换 nums[i-1] 和 nums[j]
  4. 通过 reserve(),将 nums[i] 至nums 尾部的序列改为顺序排列。(因为本身为逆序排列,所以只需要双指针分别从首位依次遍历置换即可)
class Solution {
    public void nextPermutation(int[] nums) {
        //从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界
        int i = nums.length-1;
        while(i>0 && nums[i] <= nums[i-1]) {
            i--;
        }
        
        //把逆序区域的前一位和逆序区域中刚刚大于它的数字交换位置
        if(i>0) {
            int j = nums.length-1;
            while(j>0 && nums[j] <= nums[i-1]) {
                j--;
            }
            swap(nums, i-1, j);
        }
        //把原来的逆序区域转为顺序
        reserve(nums, i);
    }
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    private void reserve(int[] nums, int i) {
        int j =nums.length-1;
        while(i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }
}

字典序算法

何为字典序列

字典序,是基于字母顺序排列的单词按字母顺序排列的方法。 这种泛化主要在于定义有序完全有序集合(通常称为字母表)的元素的序列(通常称为计算机科学中的单词)的总顺序。
对于数字1、2、3…n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。

  1. 单个字符
    在计算机中,25个字母以及数字字符,字典排序如下:
    ‘0’ < ‘1’ < ‘2’ < … < ‘9’ < ‘a’ < ‘b’ < … < ‘z’
  2. 多个字符
    在计算机中,两个字符串比较大小,是按照从左到右的顺序进行比较,如果第1位相等,就比较第2位,直至有一位可以比较出大小来,则不再继续比较。
    对于任意两个序列 (a,b) 和 (a’,b’),字典序定义为: (a,b) ≤ (a′,b′) 当且仅当 a < a′ 或 (a = a′ 且 b ≤ b′).
    比如 ‘ab’ < ‘ac’、‘abc’ < ‘ac’、‘abc’ < ‘abcd’
  3. 全排列的字典序
    给定多个字符,可以按照任意顺序进行排列,所有排列称为全排列。
    每一种排列对应一个字符串,如果这些字符串按照字符串大小的顺序进行排序,那么就这种排序是基于字典序的全排列。
    比如给定三个字符 a,b,c,则他们基于字典序的全排列为:
    abc > acb > bac > bca > cab > cba
字典序算法

字典序算法用来解决这样一个问题:给定其中一种排列,求基于字典序的下一种排列。
比如给定一种排列为 abc,则其基于字典序的下一种排列为 acb。
要求下一种排列既要比原排列大,又不能有第三种排列位于他俩之间。即下一种排列为大于原排列的最小排列。

以输入为 358764 为例,字典序算法的步骤为:

3	5	8	7	6	4
	↑			↑
   i-1			j

(交换 i-1,j 位置)
3	6	8	7	5	4
		↑
		i

(将 i 及其后面的数变为顺序排列)
3	6	4	5	7	8
  1. 从原排列中,从右至左,找到第一个大于左邻的字符位置 i,记左邻位置为 i-1。其中 i 为逆序区域的第一个字符 (如例中 i=2,nums[i-1] = 5。)
  2. 重新从右至左,找到第一个比 nums[i-1] 大的字符,记为位置为 j。(如例中 j=4,list[j] = 6。)
  3. 交换 i-1 和 j 两个位置的值。(如例中变为 368754。)
  4. 将 i 及其后面的数,由小到大排列。(如例中变为 364578,最终输出 364578。)

注意:

  1. 第1步中,如果找不到右邻大于左邻的数,则说明给定的排列已经是全排列的最后一个排列了,则直接返回全排列的第一个排列,即所有排列中最小的排列,形成一个循环。
  2. 在第3步交换前,i-1 后面的数是按照从大到小进行排列(否则第1步中就可以找到右邻大于左邻的数了)。
  3. 在交换之后,i 及其后面的数仍然是按照从大到小排列的,尽管 j 位置的值变成了 nums[i-1],但是由于 j 位置是第一个比 nums[i-1] 大的,因此交换之后 lnums[i-1] 仍然比左邻小,比右邻大。
  4. 既然 i 及其后面的数是从大到小排列的,那么第4步的排序,直接将 i 及其后面的数倒序即可。

LeetCode 31. Next Permutation 下一个排列(Java)_第1张图片
总结算法思路为:
1. 从后向前查看逆序区域,找到逆序区域的前一位,也就是数字置换的边界
2. 把逆序区域的前一位和逆序区域中刚刚大于它的数字交换位置
3. 把原来的逆序区域转为顺序

时间复杂度:O(n) + O(n) + O(n) = O(n),在最坏的情况下,只需要对整个数组进行两次扫描
空间复杂度:O(1),没有使用额外的空间,原地替换足以做到

你可能感兴趣的:(LeetCode)