Leetcode(31)——下一个排列(图解字典序算法)

       关于Leetcode的刷题方法,在网上看了很多,每个人都有着不同的见解。作为Leetcode小白,我还是决定用最原始的方法进行刷题,即把每一题的思路都记录下来,虽然比较耗时间,但是个人感觉这样子的方法会让我对题目有更深入的了解。希望能在该专栏中记录下刷题过程中遇到的大大小小的问题🍊。

题目描述

在这里插入图片描述


解题思路

什么是字典序算法

前情提要

  • 在学字符串string的时候,我们肯定接触过两个字符串之间的比较,比如”abc“ < “acb” < “acbd”,其规则是先比较第一个字母,如果不相等,就直接得到结果,如果相等,就比较下一个字母。
  • 如果两个字符串的长度不相等,但是长的那个字符串包含了短的那个,那长的那个字符串更大(比如"acb" < “acbd”)
  • 在我们进行比较之前,有一个默认的排序规则,就是‘a' < 'b' < 'c' < ... < 'z'

理解字典序算法

  • 我们回到本题,说实话刚看题目我看了半天不知道在说什么,看到评论里面提到字典序算法才知道题意。我们拿题目中的例子1,2,3 ------> 1,3,2来说明:
    1. 首先本题讨论的范围是数字,数字中有一个规则,就是’0‘ < '1' < '2' < ... < '9',这与上面的a~z是一样的
    2. 然后就是1,2,3这三个数字,我们能够形成6种不同的组合,即123 < 132 < 213 < 231 < 312 < 321
    3. OK,如果你看懂前面两点,本题已经完成了。我们要做的就是找到当前数123在第二点的六种排列中间的下一个位置是什么,即132,那么132就是答案
    4. 如果要找的数字位于排列组合的最后一个一位,即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());
        }
    }
};
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值