Leetcode-31. 下一个排列

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

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

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

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。

1
2
3
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

来源:力扣(LeetCode)
链接:31. Next Permutation

解题思路

因为还没有系统地学习过离散数学,看到这样的题目通常都是一脸茫然的,参考了wiki百科Permutation以及《离散数学及其应用》第七版P368~369上面的算法介绍, 按字典顺序生成下一个最大排列的算法如下:

  • Find the largest indexksuch that a[k] < a[k + 1]. If no such index exists, the permutation is the last permutation.
  • Find the largest index l greater than k such that a[k] < a[l].
  • Swap the value of a[k] with that of a[l].
  • Reverse the sequence from a[k + 1] up to and including the final element a[n].

再来看下面一个例子,有如下的一个数组

1
1  2  7  4  3  1

下一个排列为:

1
1  3  1  2  4  7

看看这个算法是如何做到的.

  • 首先, 满足a[k] < a[k + 1]的最后一对整数分别为a[2]=2和a[3]=7
  • 而排在a[2]右边且大于a[2]的最小整数是a[5]=3
  • 交换a[2]a[5]
  • 然后将剩余的数字进行升序即可

知道了这个算法以后代码写起来应该就比较容易了,具体如下:

代码

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
45
46
47
48
49
50
51
52
53
// 交换nums数组当中的两个元素
void swap(int* nums, int a, int b)
{
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
// 对 nums 数组的 start 到 end 做倒序排列
void reverse(int* nums, int start, int end)
{
while(start < end)
{
swap(nums, start++, end--);
}
}


void nextPermutation(int* nums, int numsSize)
{
if(!nums || numsSize == 0) return;

int firstIndex = -1, secondIndex = -1;


for(int i = numsSize - 2; i >= 0; i--)
{
if(nums[i] < nums[i + 1])
{
firstIndex = i;
break;
}
}

if(firstIndex == -1)
{
reverse(nums, 0, numsSize - 1);
return;
}

for (int i = numsSize - 1; i >= firstIndex; i--)
{
if (nums[i] > nums[firstIndex])
{
secondIndex = i;
break;
}
}

swap(nums, firstIndex, secondIndex);
reverse(nums, firstIndex + 1, numsSize - 1);
return;

}

参考