Leetcode31. Next Permutation | Binary Search, Two Pointer

1.Description&Analysis

这道题有点像模拟,要从例子入手。自己写一下简单的数列推一下分析规律。我是用123456这样推的,因为太短的看不出东西。最开始我们从123开始推。
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
我们这么写,背后的原因是什么?首先最高位的选择是从最小的数字选到最大数字。从次高位开始,从第二小、第三小的数字依次排列到最后一位。这就是第一个排列。

从第一个排列到第二个排列的时候,我们颠倒了2和3的位置,这并不是因为最高位不动只剩两个位置。我们的考量是:如果随着枚举的进行,已经没法遵照越小数字匹配越高数位的原则,那么就优先把次高的数字放到它能去的最低的数位上。比如:
1 2 3 4 5 6
1 2 3 4 6 5
1 2 3 5 4 6
1 2 3 5 6 4
从465到546,是因为最低的两个数位已经枚举完了,必须要对倒数第三低的数位进行操作了。那么为了保证小,肯定是从剩下的数字里(5和6,4不能重复站在那里了)选最小的,然后让它站到原本的守门员(4)的位置上。剩下的数字容易想到,它们必然是继续遵照“越小的数字站越高的数位”的原则进行。

这个子问题的分解其实有点DP的感觉,最初我也以为是DP,从最后的结束位开始考虑,试图分解子问题,但没有成功。但这里的思路其实是一样的,就是我每次只盯着“不得不动、并且最低的数位”,完成了对它的替换之后,它后面的那一节就是一个子序列,而我们已经解决了,答案就是“越小数字站越高数位”,所以每次你就只用考虑“守门员”和“下一位守门员”。

那么如何来找守门员呢?回想我们写数字的过程,当后面x个数位构成的子序列已经最优(也就是已经越大站越高数位)之后,x-1这个位置的数就成了守门员。所以,从数列最后向前找,打破递增的那个数字就是守门员。

如何找下一位守门员?下一位守门员需要满足两个条件:1.大于x-1位置的数字 2.是所有大于x-1位置的数字里最小的。下一位守门员一定出现在倒数第二个位置吗?不是的,写几个例子就明白了。因为这个子序列它此刻已经是最优的(递减),作为一个有序数列,查找指定元素,容易想到二分查找。

找到两个数字之后,要让他们交换。注意,交换完了,x-1之后的子序列此时依然是一个递减序列。因为被选走的下一位守门员是剩下所有数字里最小的,现在比他小的守门员来了,肯定依然是最小的。所以下一步只要颠倒子序列就好了。这里其实有点类似进位之后的清零。
在这里插入图片描述

概括一下流程:
1.找到以最后一个元素为结束的递减子序列。若这个递减子序列的首个元素为n,n-1就是下一轮要变动数位的元素。
2.通过二分查找,找到该递减子序列里所有大于n-1的元素中最小的那个,下标记录为ans。
3.ans与原n-1互换。
4.使用双指针对该子序列进行颠倒。

2.用到的算法

二分查找,双指针。

3.代码
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        if(nums.size() == 1)
            return;
        
        int n = nums.size()-1;
        while(n>0 && nums[n-1]>=nums[n]) 
            n--;
        
        int j = 0, i = nums.size()-1,tmp = 0;
        //check if it is an descending array
        if(n == 0){
            while(j < i){
                tmp = nums[j];
                nums[j] = nums[i];
                nums[i] = tmp;
                j++;
                i--;
            }
            return;
        }
        
        //notice that nums[n-1] is the dividing element;
        //find the nearst element which is bigger than nums[n] from [n+1, nums.size()-1] using binary search;
        int left = n, right = nums.size()-1, mid = 0, ans = 0;
        while(left <= right){
            mid = left + (right-left)/2;
            if(nums[mid] <= nums[n-1]){
                right = mid-1;
            }
            else{
                left = mid+1;
                ans = mid;
            }
        }
        
        tmp = nums[n-1];
        nums[n-1] = nums[ans];
        nums[ans] = tmp;
        
        j = n, i = nums.size()-1;
        while(j < i){
            tmp = nums[j];
            nums[j] = nums[i];
            nums[i] = tmp;
            j++;
            i--;
        }
        
    }
};
4.复杂度

Time Complexity: O(n)
Space Complexity: O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值