题目分析

题目链接:31. Next Permutation

这题让我们找到比输入数字排列恰好大一点点的数字排列。对这个问题的算法不仅适用于数字串,而且适用于任何有字典序的符号串。

为了方便讨论,我们将输入数字串称为串1,输出数字串称为串2。

输出的串2需要满足以下条件:

  1. 字典序增大。
    字典序增大当且仅当:串2与串1从最高位的数开始一一对比,第一个不同的数字(假设是从左数第k位),必定是串2的第k位比串1的大。比如串1:214653与串2:215346,第一个不同的数字是从左数第三位,串2的第三位必须比串1的第三位大。
  2. 字典序的增大得尽可能少。
    要满足以下3个子条件(优先满足1,然后满足2,最后满足3):

    1. k尽可能地大。也就是说,串1和串2第一个不同的数字,尽可能来得晚一些。字典序越往右边权重越低,增加串1尽可能右边的数字。
    2. 串2的第k位数不但要比串1的大,而且要尽可能地小。因为第k位是第一个不同的数字,所以它的大小直接决定串2增加多少。
    3. 串2的第k位右边的部分,应该处于字典序最小的排列。因为第k位已经能够决定串2比串1大,所以第k位右边的那些数字排列不影响串1与串2之间的大小关系。

代码实现

class Solution
{
public:
  void nextPermutation(vector<int> &nums)
  {
    int len = nums.size(), k = len - 2;
    if (len <= 1)
      return;
    // 找出k
    // 从右往左扫描,如果第k位数不是已经扫描到的数字中最大的,说明第k位数可以增大(只要将【已经扫描到的、比第k位大的数】与【第k位数】置换)
    while (k >= 0 && nums[k] >= nums[k + 1])
    {
      --k;
    }
    if (k < 0)
    {
      // k < 0 说明输入串已经是递减的,无法再增大字典序
      reverse(nums.begin(), nums.end());
      return;
    }

    int bigger = k;
    // 从第k位的右边找到一个【恰好比第k位大一点点】的数字,与第k位置换
    while (bigger + 1 < len && nums[bigger + 1] > nums[k])
    {
      ++bigger;
    }
    swap(nums[k], nums[bigger]);
    // 此时第k位右边的数字串必定是递减的(最大字典序),我们只要逆转一下就变成递增的(最小字典序)
    reverse(nums.begin() + k + 1, nums.end());
    return;
  }
};

算法需要做2次扫描(O(n)),再做一次数组逆转(O(n))。因此总的时间复杂度为O(n)。其中n为输入数字串的长度。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.