Leetcode-031-下一个排列

Leecode-031-下一个排列

题目描述

  • 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

  • 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

  • 必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

字典序

1
2
3
4
5
6
本题要求我们实现一个算法,将给定数字序列重新排列成字典序中下一个更大的排列。

以数字序列 [1,2,3][1,2,3] 为例,其排列按照字典序依次为:
[1,2,3]\\ [1,3,2]\\ [2,1,3]\\ [2,3,1]\\ [3,1,2]\\ [3,2,1]

这样,排列 [2,3,1] 的下一个排列即为 [3,1,2]。特别的,最大的排列 [3,2,1] 的下一个排列为最小的排列 [1,2,3]。

方法:两边扫描

注意到下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:

  1. 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
  2. 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小
  3. 当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅
1
2
3
4
5
以排列 [4,5,2,6,3,1] 为例:

我们能找到的符合条件的一对「较小数」与「较大数」的组合为 2 与 3,满足「较小数」尽量靠右,而「较大数」尽可能小。

当我们完成交换后排列变为 [4,5,3,6,2,1],此时我们可以重排「较小数」右边的序列,序列变为 [4,5,3,1,2,6]。

具体地,我们这样描述该算法,对于长度为 n的排列 a:

  1. 首先从后向前查找第一个顺序对 (i,i+1),满足 a[i] < a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n)必然是下降序列。

  2. 如果找到了顺序对,那么在区间[i+1,n)中从后向前查找第一个元素 j 满足 a[i] < a[j]a[i]<a[j]。这样「较大数」即为 a[j]

  3. 交换a[i] 与 a[j],此时可以证明区间[i+1,n)必为降序。我们可以直接使用双指针反转区间 [i+1,n)使其变为升序,而无需对该区间进行排序。

mark

注意

如果在步骤 1 找不到顺序对,说明当前序列已经是一个降序序列,即最大的序列,我们直接跳过步骤 2 执行步骤 3,即可得到最小的升序序列。

该方法支持序列中存在重复元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;

// 从后向前扫描 找到较小数
while(i >= 0 && nums[i] >= nums[i + 1]){
i--;
}

// 此时(i + 1,n] 一定是下降序列
if(i >= 0){ // 如果这个条件不满足,说明当前序列已经是一个降序序列,即最大的序列 ,所以直接进行翻转reverser即可
// 从[i + 1,n)从后往前查找
// 找到丢一个元素j 满足 a[i] < a[j]
// 较大数即为 a[j]
int j = nums.length - 1;
while(j >= 0 && nums[i] >= nums[j]){
j--;
}
// 交换a[i] 和 a[j]
swap(nums,i,j);
}
// 此时[i + 1,n) 一定是降序序列
reverse(nums,i + 1);
}


public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}

// 使用双指针翻转区间[i + 1,n)
// 使得该区间变成升序序列,而无需对区间进行排序
public void reverse(int[] nums,int start){
int left = start;
int right = nums.length - 1;
while(left < right){
swap(nums,left,right);
left++;
right--;
}
}
}

复杂度分析

  • 时间复杂度:O(N),其中 N为给定序列的长度。我们至多只需要扫描两次序列,以及进行一次反转操作。
  • 空间复杂度:O(1),只需要常数的空间存放若干变量。
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信