LeetCode 31: Next Permutation 解法介绍

LeetCode 问题 31 是一道考察排列以及数组操作知识的综合性题目。最佳解法可以使用 \(O(n)\) 的时间复杂度和常数的空间复杂度解决该问题。本文给出关于该问题的详细解释。

问题描述

实现排列组合算法,给出题目提供数组中后续的排列情况。

如果没有这样的排列,则必须将所有数组重新组合为最小的那种情况,以从头开始(比如,将这些数组按照递增方式排列)。

替换操作必须就地进行且只能用到常数的额外内存空间。

示例1:
输入: nums = [1,2,3]
输出: [1,3,2]

示例2:
输入: nums = [3,2,1]
输出: [1,2,3]

示例3:
输入: nums = [1,1,5]
输出: [1,5,1]

示例4:
输入: nums = [1]
输出: [1]

约束条件:
1 <= nums.length <= 100
0 <= nums[i] <= 100

参考题解

我们以 [1,2,3,4] 做排列为例:

1  2  3  4<
1  2  4< 3
1  3  2  4<
1  3  4< 2
1  4  2  3<
1  4< 3  2
2  1  3  4<
2  1  4< 3
2  3  1  4<
2  3  4< 1
2  4  1  3<
......
4  3  1  2<
4< 3  2  1
1  2  3  4

我们以一种全新的视角来看待排列(Permutation)问题:排列问题依序组合这些元素,使这些元素以大端序排列时,给出的组合是递增的——也就是说,对于数字 123 的组合,越靠左的数位的权重越高,所以 213 的权重高于 132,同样地,231 的权重则高于 213

我们来观察进行排列时,上一组数字进行交换的位置的规律。不难发现,如果我们从右向左地查看这些数值时,将最后一个递增的数字做标记,那么下一组数字的交换位置将会是这个递增数字的左侧一个位置。我们将这个倒序查看时递增的数字的下标记作 i

我们来以 1 2 4 3 为例,不难发现 i=2,也就是对应数字 4。而在 1 2 4 3 之后的组合 1 4 2 3 中,我们注意到最后递增的数字 4 跑到了原先其左侧 2 的位置中。而在新组合 1 4 2 3 中,数字 4 后的序列 2 3 则是递增排列的。

更进一步地,我们以 2 3 4 1 到 2 4 1 3 的变化来细化这种规律。在 2 3 4 1 这组数中,i=2,对应数字 4。而我们注意到在变化后的序列中,原先 4 左侧的数字 3 则跑到了最后一位中。也就是说,在新组合后的数字保持递增排序的过程中,也要考虑到原先 i-1 位置的数字插入到新组合哪个位置的问题。至此,我们已经找到数列变化的规律了。

最优的算法的复杂度有多高呢?由于我们需要扫描两遍数组:第一遍找到递增数列的最终位置,第二遍将本身依序排列的数组反相排列,这两步操作均为线性时间复杂度。所以该算法需要 \(O(n)\) 的时间复杂度和常数项的(不需要额外空间的)空间复杂度。

解法示例

C++

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
    	int n = nums.size(), k, l;
    	for (k = n - 2; k >= 0; k--) {
            if (nums[k] < nums[k + 1]) {
                break;
            }
        }
    	if (k < 0) {
    	    reverse(nums.begin(), nums.end());
    	} else {
    	    for (l = n - 1; l > k; l--) {
                if (nums[l] > nums[k]) {
                    break;
                }
            } 
    	    swap(nums[k], nums[l]);
    	    reverse(nums.begin() + k + 1, nums.end());
        }
    }