文章目录
关于Leetcode的刷题方法,在网上看了很多,每个人都有着不同的见解。作为Leetcode小白,我还是决定用最原始的方法进行刷题,即把每一题的思路都记录下来,虽然比较耗时间,但是个人感觉这样子的方法会让我对题目有更深入的了解。希望能在该专栏中记录下刷题过程中遇到的大大小小的问题🍊。
题目描述
解题思路
什么是字典序算法
前情提要
- 在学字符串string的时候,我们肯定接触过两个字符串之间的比较,比如”abc“ < “acb” < “acbd”,其规则是先比较第一个字母,如果不相等,就直接得到结果,如果相等,就比较下一个字母。
- 如果两个字符串的长度不相等,但是长的那个字符串包含了短的那个,那长的那个字符串更大(比如"acb" < “acbd”)
- 在我们进行比较之前,有一个默认的排序规则,就是
‘a' < 'b' < 'c' < ... < 'z'
理解字典序算法
- 我们回到本题,说实话刚看题目我看了半天不知道在说什么,看到评论里面提到字典序算法才知道题意。我们拿题目中的例子
1,2,3 ------> 1,3,2
来说明:- 首先本题讨论的范围是数字,数字中有一个规则,就是
’0‘ < '1' < '2' < ... < '9'
,这与上面的a~z是一样的 - 然后就是1,2,3这三个数字,我们能够形成6种不同的组合,即
123 < 132 < 213 < 231 < 312 < 321
。 - OK,如果你看懂前面两点,本题已经完成了。我们要做的就是找到当前数123在第二点的六种排列中间的下一个位置是什么,即132,那么132就是答案
- 如果要找的数字位于排列组合的最后一个一位,即321,那么按照题目的第二行,我们就返回最小值123.
- 首先本题讨论的范围是数字,数字中有一个规则,就是
抽象字典序算法
- 上面从直觉上理解了什么是字典序算法,下面说下怎么转化成程序算法。
何时无解
- 首先考虑无解情况,即上面所说的321,这种情况带入字典序算法是无解的,而321这种情况,如果我们单独拆分成3,2,1三个数字,其实是一个降序的过程:
- 因此如果当前排列是降序的,则字典序算法无解
- 换而言之,如果不存在后一个数比前一个数大(2<3,1<2),字典序算法无解
- 比如下图中的54321抽象出来的五个点,不存在后一个点大于前一个点,因此无解。
有解的情况
- 下面我们拿51432这个例子,来一步步说明如何通过字典序算法得到52134这个答案的
1.从右往左找,找到第一个右边比左边大的数
- 首先我们从最右边的2开始,因为2 < 3,因此跳过。然后3 < 4,再跳过。然后发现4 > 1,OK,第一步完成。
- 然后我们在上图中用黄色点标记这两个数,即1和4
2.找到左边黄点的右边所有数中最小的一个
- 如果我们直接交换两个黄点,得到54132,虽然也比51432大,但是它不符合字典序算法中的规则,因为这两个数中间还夹杂着别的数
51432 < 52134 < 52143 < 52314 < ...... < 54132
,字典序算法中必须满足两个数之间不能夹杂其他数才行。 - 而根据上面列举的,我们知道52134才是我i们想要的答案,它的特点就是我们需要把1换成2,而不是4。
- 而2实际上就是4,3,2中间的最小值,因此这一步我们要做的就是找到左边黄点(1)右边的所有数(4,3,2)中间最小的一个数(2),然后我们用 红点标记下来。
- 之所以要交换1和2,而不是1和3或者1和4,是因为我们现在是要把千位的1换成一个更大中的最小的情况,因此要选2.
3.交换左边的黄点和红点
- 上面我们找到了红点(2),因此这一步我们需要讲红点跟左边的黄点(2)进行交换,得到52431
4.对红点右边的数进行升序排序
- 此时,我们交换了千位的1和个位2,变成了52431.但是距离我们的最终答案52134还差了一步,
52134 < 52143 < 52314 < 52341 < 52413 < 52431
,👈由这个规则可以看到我们的千位和万位已经相同了,但是个十百位还未相同了,而因为我们要找的答案要尽可能小,因此需要进行升序排序,得到52134,大功告成!
题目代码
class Solution {
public:
void nextPermutation(vector<int>& nums) {
bool flag = 0; //默认无解
for(int i=nums.size() - 1;i > 0;i--){ //从右往左找右边比左边大的情况
if(nums[i] > nums[i - 1]){
flag = 1; //有解
int min = nums[i],idx = i; //初始化最小值为右边黄点(nums[i])
for(int j = i + 1;j < nums.size();j++){ //找到左边黄点的右边所有数的最小值
if(nums[j] < min && nums[j] > nums[i - 1]){
min = nums[j]; //更新最小值
idx = j; //储存下标,便于后续的交换
}
}
//交换左边的黄点和红点
nums[i - 1] ^= nums[idx];
nums[idx] ^= nums[i - 1];
nums[i - 1] ^= nums[idx];
//对交换后的红点右边的数进行升序排序
sort(nums.begin() + i,nums.begin() + nums.size());
break; //跳出循环,不用再找
}
}
if(flag == 0){ //无解则返回最小值,即升序的情况
sort(nums.begin(),nums.begin() + nums.size());
}
}
};