题目描述
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
-
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
-
1,2,3 → 1,3,2
-
3,2,1 → 1,2,3
-
1,1,5 → 1,5,1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
符合直觉的方法是我们按顺序求出所有的排列,如果当前排列等于 nums,那么我直接取下一个 但是这种做法不符合 constant space 要求(题目要求直接修改原数组),时间复杂度也太高,为 O(n!),肯定不是合适的解。
这种题目比较抽象,写几个例子通常会帮助理解问题的规律。我找了几个例子,其中蓝色背景表示的是当前数字找下一个更大排列的时候 需要改变的元素
.
我们不难发现,蓝色的数字都是从后往前第一个不递增的元素,并且我们的下一个更大的排列 只需要改变蓝色的以及之后部分即可,前面的不需要变。
那么怎么改变蓝色的以及后面部分呢?为了使增量最小, 由于前面我们观察发现,其实剩下的元素从左到右是递减的,而我们想要变成递增的,我们只需要不断交换首尾元素即可。
另外我们也可以以回溯的角度来思考这个问题,让我们先回溯一次:
这个时候可以选择的元素只有2,我们无法组成更大的排列,我们继续回溯,直到如图:
我们发现我们可以交换4或者2实现变大的效果,但是要保证变大的幅度最小(下一个更大), 我们需要选择最小的,由于之前我们发现后面是从左到右递减的,显然就是交换最右面大于1的。
之后就是不断交换使之幅度最小:
关键点解析
-
写几个例子通常会帮助理解问题的规律
-
在有序数组中首尾指针不断交换位置即可实现reverse
-
找到从右边起
第一个大于nums[i]的
,并将其和nums[i]进行交换
代码
-
/*
-
* @lc app=leetcode id=31 lang=javascript
-
*
-
* [31] Next Permutation
-
*/
-
-
function reverseRange(A, i, j) {
-
while (i < j) {
-
const temp = A[i];
-
A[i] = A[j];
-
A[j] = temp;
-
i++;
-
j--;
-
}
-
}
-
/**
-
* @param {number[]} nums
-
* @return {void} Do not return anything, modify nums in-place instead.
-
*/
-
var nextPermutation = function(nums) {
-
// 时间复杂度O(n) 空间复杂度O(1)
-
if (nums == null || nums.length <= 1) return;
-
-
let i = nums.length - 2;
-
// 从后往前找到第一个降序的,相当于找到了我们的回溯点
-
while (i > -1 && nums[i + 1] <= nums[i]) i--;
-
-
// 如果找了就swap
-
if (i > -1) {
-
let j = nums.length - 1;
-
// 找到从右边起第一个大于nums[i]的,并将其和nums[i]进行交换
-
// 因为如果交换的数字比nums[i]还要小肯定不符合题意
-
while (nums[j] <= nums[i]) j--;
-
const temp = nums[i];
-
nums[i] = nums[j];
-
nums[j] = temp;
-
}
-
-
// 最后我们只需要将剩下的元素从左到右,依次填入当前最小的元素就可以保证是大于当前排列的最小值了
-
// [i + 1, A.length -1]的元素进行反转
-
-
reverseRange(nums, i + 1, nums.length - 1);
-
};
相关题目
-
46.next-permutation
-
47.permutations-ii
-
60.permutation-sequence(TODO)