31. Next Permutation(下一个排列)解法(C++ & 注释)

31. Next Permutation(下一个排列)

  • 1. 题目描述
  • 2. 一次遍历(Single Pass Approach)
    • 2.1 解题思路
    • 2.2 实例代码

1. 题目描述

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

题目链接:中文题目;英文题目

2. 一次遍历(Single Pass Approach)

2.1 解题思路

这道题的难点应该是理解题目在说什么2333。题目重新解释,来源@Susan的评论:

题干的意思是:找出这个数组排序出的所有数中,刚好比当前数大的那个数
————
比如当前 nums = [1,2,3]。这个数是123,找出1,2,3这3个数字排序可能的所有数,排序后,比123大的那个数 也就是132
————
如果当前 nums = [3,2,1]。这就是1,2,3所有排序中最大的那个数,那么就返回1,2,3排序后所有数中最小的那个,也就是1,2,3 -> [1,2,3]

所以我们可以观察一下下面这个例子:

2, 3, 5, 4, 1, 9, 8, 7

从后往前,9, 8, 7是升序,当遇到1时,整个数组变成降序,相当于形成了一个谷底,然后我们在9, 8, 7中选择一个比1大,但是又在三个数中最小的数,即7。交换1和7后,我们把后面的数(9, 8, 1)变成升序(从前往后)就得到了最后答案。

所以解题思路是:

  1. 从后往前,找到第一个变小的数nums[smallerIdx]和对应序号(smallerIdx);
  2. 如果找不到,则说明整个数组是降序排序,直接升序排序即可完成;
  3. 然后从smallerIdx + 1开始,寻找一个比 nums[smallerIdx]大,但是在后面的数中最小的数nextLargerNumber和序号(largerIdx);
  4. 交换nums[smallerIdx]和nums[largerIdx];
  5. 升序排序smallerIdx后面的数,后者反转也行;

所以说我们只需要从后往前找到第一个谷底数就可以了,它之前的数已经不影响最后的结果。

注意一下:可以用sort升序排序,也可以使用reverse反转数组,两者结果都是一样的,只是代码中的一个边界判断不大一样。

2.2 实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
    // reverse和sort二选一即可
    void reverse(vector<int>& nums, int start) {
        int end = nums.size() - 1;
        while (start < end) {
            swap(nums[start], nums[end]);
            start++;
            end--;
        }
    }

public:
    void nextPermutation(vector<int>& nums) {
        int len = nums.size(), smallerIdx = -1, largerIdx = -1;
        // 从后往前找到谷底
        for (int i = len - 1; i > 0; i--) {
            if (nums[i] > nums[i - 1]) { smallerIdx = i - 1; break; }
        }

        // 原数组为降序,直接反转即可
        if (smallerIdx == -1) {
            sort(nums.begin(), nums.end());
            //reverse(nums, 0);
            return;
        }

        // 找到在剩余数字中最小的,且比谷底数要大的数
        int nextLargerNumber = nums[smallerIdx];
        for (int j = smallerIdx + 1; j < len; j++) {
            if (nums[j] > nums[smallerIdx]) {
                // 使用reverse需要nums[j] <= nextLargerNumber,因为需要保证交换的数为序号最大的那个,否则后面反转会出错;比如[2,3,1,3,3]
                //如果使用sort只需nums[j] < nextLargerNumber
                if (nextLargerNumber == nums[smallerIdx] || nums[j] <= nextLargerNumber) { nextLargerNumber = nums[j]; largerIdx = j; }
            }
        }

        swap(nums[smallerIdx], nums[largerIdx]);

        sort(nums.begin() + smallerIdx + 1, nums.end());
        //reverse(nums, smallerIdx + 1);
    }
};