目录
题目要求
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100来源:力扣(LeetCode)
看完题目可能一头雾水,什么是字典序数?什么又是下一个排列,很多读者连题目都没看懂。不急本篇的讲解现在开始
题目理解以及思路分析
(一) 先来搞懂题目,搞懂了题目就已经成功了一半了。
在数学中,字典或词典顺序(也称为词汇顺序,字典顺序,字母顺序或词典顺序)是基于字母顺序排列的单词按字母顺序排列的方法。 这种泛化主要在于定义有序完全有序集合(通常称为字母表)的元素的序列(通常称为计算机科学中的单词)的总顺序。
对于数字1、2、3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。
(来源:百度百科)
就拿1 2 3 这个序数来讲解吧
其按照从小到大的顺序排列的顺序为:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
可以发现什么规律?
1.全数升序排列是最小的,全数逆序排列是最大的
2.第一个数字越大其字典序越大
3.后面的数字升序排列比逆序排列小
这些是我们通过这个例子能找到的规律,但是对我们解决这道题目有什么帮助呢?我们接着往下看
(二) 理解了题目的意思,接下来就是如何解决题目中的问题了。我们需要找到给出的序列其下一个排列,其下一个排列一定大于原序列,除非原序列是最大的。但是也需要注意一点,其下一个排列一定是 变化最小的 比其原序列更大的序列。所以我们必须保证其变大的幅度尽可能最小。
(三) 那么问题又来了,我们如何保证其变大的幅度最小。别急,下面将介绍一个算法。对于一个给定的序列,通过上面例子的理解以及我们找到的规律很显然,我们需要找到两个数——“ 左边的较小数” 和 “右边的较大数”,并且需要保证“左边的较小数”要靠近右边,“右边的较大数”要尽可能的小,找到后将其互换位置。
以 4,5,2,6,3,1 这个序列为例,较小数为 2 ,较大数为 3,二者互换得到新的序列 4,5,3,6,2,1 这个时候我们将 3 后面数字重新按照 升序 的顺序排列,这样最终得到 4,5,3,1,2,6 这个序列,这个便是我们想要得到的序列。(至于为什么要按照升序排列,难道忘了上面我们找到的规律了吗?)
(四) 知道了这个思路我们便开始解决如何找到这两个数字。也很简单,仔细观察序列,
(1)我们按照从后往前的顺序去找,找到 第一个 左边数字小于右边的数字的 数字 a[ i ],并记录下来该位置为 i 。
(2)然后再次从后往前开始寻找(直至遍历到 位置 n 时为止),找到第一个大于 a[ n ] 的数字 a[ j ] ,记录下来该位置。
(3)二者互换,最后将 a[ j ] 位置之后的数字按照 升序 的顺序重新排列。就得到了我们想要得到的序列。
为了便于大家理解,我做了下面图片供大家理解。
明白了这些之后,我们便开始进行代码的实现。
代码分部讲解
第一部分
void swap(int *a, int *b)
{
int t = *a;
*a = *b, *b = t;
}
这部分很容易理解,就是二者互换顺序的过程。
第二部分
void reverse(int *nums, int left, int right)
{
while (left < right)
{
swap(nums + left, nums + right);
left++, right--;
}
}
这一部分很巧妙,因为后面要进行 升序 排列,但是细心观察序列的每一次变化,结合上面的图解,我们可以发现,在我们将找到的 较小数 和 较大数 二者互换后,其后面的序列就已经是 逆序 排列的了,因此我们便可以通过反转来达到 升序 排列的目的,这样使得程序更简洁。
第三部分
void nextPermutation(int *nums, int numsSize) {
int i = numsSize - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) //找 较小数
{
i--;
}
if (i >= 0)
{
int j = numsSize - 1;
while (j >= 0 && nums[i] >= nums[j]) //找 较大数
{
j--;
}
swap(nums + i, nums + j); //调用函数 实现二者互换
}
reverse(nums, i + 1, numsSize - 1); //调用函数 实现 升序 排列
}
这一步也很好理解,主要是找 较小数 和 较大数 的过程,可以有很多方法,这里只是其中一种。
第四部分
附上完整的代码
void swap(int *a, int *b)
{
int t = *a;
*a = *b, *b = t;
}
void reverse(int *nums, int left, int right)
{
while (left < right)
{
swap(nums + left, nums + right);
left++, right--;
}
}
void nextPermutation(int *nums, int numsSize) {
int i = numsSize - 2;
while (i >= 0 && nums[i] >= nums[i + 1])
{
i--;
}
if (i >= 0)
{
int j = numsSize - 1;
while (j >= 0 && nums[i] >= nums[j])
{
j--;
}
swap(nums + i, nums + j);
}
reverse(nums, i + 1, numsSize - 1);
}