31. 下一个排列

题目描述

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

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

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

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

来源:力扣(LeetCode)

思路

符合直觉的方法是我们按顺序求出所有的排列,如果当前排列等于 nums,那么我直接取下一个 但是这种做法不符合 constant space 要求(题目要求直接修改原数组),时间复杂度也太高,为 O(n!),肯定不是合适的解。

这种题目比较抽象,写几个例子通常会帮助理解问题的规律。我找了几个例子,其中蓝色背景表示的是当前数字找下一个更大排列的时候 需要改变的元素.

640?wx_fmt=png

我们不难发现,蓝色的数字都是从后往前第一个不递增的元素,并且我们的下一个更大的排列 只需要改变蓝色的以及之后部分即可,前面的不需要变。

那么怎么改变蓝色的以及后面部分呢?为了使增量最小, 由于前面我们观察发现,其实剩下的元素从左到右是递减的,而我们想要变成递增的,我们只需要不断交换首尾元素即可。

另外我们也可以以回溯的角度来思考这个问题,让我们先回溯一次:

640?wx_fmt=png

这个时候可以选择的元素只有2,我们无法组成更大的排列,我们继续回溯,直到如图:

640?wx_fmt=png

我们发现我们可以交换4或者2实现变大的效果,但是要保证变大的幅度最小(下一个更大), 我们需要选择最小的,由于之前我们发现后面是从左到右递减的,显然就是交换最右面大于1的。

之后就是不断交换使之幅度最小:

640?wx_fmt=png

关键点解析

  • 写几个例子通常会帮助理解问题的规律

  • 在有序数组中首尾指针不断交换位置即可实现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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值