Leetcode 31 - Next Permutation

24 篇文章 0 订阅

题意

实现next_permutation()

思路

首先,我们知道,要使下一个数尽量小,那我们就需要下一个变大的数尽量在后面。

假如我们选择位置k的数和某一个位置进行交换,我们需要考虑:为什么不选择k+1及其之后的位置?因为k+1到n的位置上的数已经达到了最大。即k+1到n的数组是降序的。比如我们k+1到n的数字为:1, 2, 3, 4。那么我们可以选择2换到前面使之增大。但是若其为:4, 3, 2, 1。很容易知道其能组成的数字已经达到了最大!所以我们要选择的位置为最后一个升序点之前的那一个点

即假如我们的值为7, 1, 3, 6, 5, 4, 2。那么我们的最后一个升序点为6,所以它之前的那个点为3,因为是升序的,我们很容易知道3和6交换能够使值变大。但是能否使值变大但是变大的最小呢?

我们可以画一个图来表示这个阶段:
这里写图片描述
很容易观察知道:我们将4和3交换位置(即在3之后,最小的并且大于3的点),然后对后面进行排序,就可以得到我们下一个排列,于是得到了我们时间复杂度为 O(nlogn) 的算法。

算法1

时间复杂度 O(nlogn)

  1. 找到最后一个升序点的前一个点A。
  2. 找到A之后最后一个最小的并且大于A的点B。
  3. 交换A和B的值,并对A之后的点从小到大排序。
算法2

时间复杂度 O(n)

我们考虑能否利用已有的性质不进行排序。

首先,我们还是和之前的算法一样:

  1. 找到最后一个升序点的前一个点A。
  2. 找到A之后最后一个最小的并且大于A的点B。

于是,我们现在的状态就是这样:
这里写图片描述
C是A之前的部分,D是AB之间的哪些部分,E是B之后的部分。

我们需要将B放到A的位置上,那么,我们的状态为:
这里写图片描述

我们需要做的就是:使G部分最小(G是由A和D和E组成的)

因为A是最后一个升序点之前的那个点,于是我们就知道:D, B, E三部分均为降序,并且有 D>B>E B>A (会存在=的情况,和>一样的)。

于是我们有如下的性质: D>A>E

即我们的G部分的组成为:
这里写图片描述
并且,由我们之前的结论知道:我们的D和E均为降序的,所以我们需要将E和Dreverse来使之为升序的(我们可以先reverse再交换AB使只reverse一次)。

所以,我们可以得到我们完整的算法2:

  1. 找到最后一个升序点的前一个点A。
  2. 找到A之后大于A并且最小的那个点B(尽量靠后)
  3. 将A之后的位置reverse。
  4. 交换A和B。

时间复杂度 O(n)

代码

algorithm 1

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size(), i = n - 2;
        //find the last increase point
        for (; i >= 0; i--) {
            if (nums[i] < nums[i + 1]) break;  
        }
        int ind = i + 1;
        for (int j = i + 1; j < n; j++) 
            if (nums[j] > nums[i] && nums[j] < nums[ind])
                ind = j;
        if (i >= 0) swap(nums[i], nums[ind]);
        if (i + 1 < n) sort(nums.begin() + i + 1, nums.end());
    }
};

algorithm 2

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size(), i = n - 2;
        for (; i >= 0; i--) {
            if (nums[i] < nums[i + 1]) break;  
        }
        int ind = i + 1;
        if (i >= 0) {
            for (int j = i + 1; j < n; j++) 
            if (nums[j] > nums[i] && nums[j] <= nums[ind])
                ind = j;
        }
        if (i + 1 < n) reverse(nums.begin() + i + 1, nums.end());
        if (i >= 0) swap(nums[i], nums[i + n - ind]);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值