[LeetCode] 31. Next Permutation

本文介绍了一种算法,用于找出整数数组的下一个字典序排列。通过从后向前查找第一个升序对,交换并反转来实现。适用于原地操作且只使用常数额外内存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接: https://leetcode.com/problems/next-permutation/

1. 题目介绍

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).
The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

给出一个int 类型的数组,求这个数组下一个字典序的排列是什么?

这个字典序是什么意思呢?我们可以这样理解:在不重复的基础上,从所有可以选择的数中,选取最小的数放在当前位置上。
假设有1、2、3 三个数。它们的排列方式有8种。

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

一开始,1 2 3

选第一个位置的时候,有{123}备选,选其中最小的 1 放在第一个数;
选第二个位置的时候,有{ 23}  备选,选最小的2放入第二个位置; 
选第三个数的时候,有{ 3 }备选,所以选其中最小的 3 放在第三个位置。

后来,1 3 2

选第一个位置的时候,有{123}备选,选其中最小的 1 放在第一个数;
选第二个位置的时候,有{ 23}  备选,但是,2 已经用过了,所以为了不重复,只能选 3 放入第二个位置; 
选第三个数的时候,有{ 2 }备选,所以选其中最小的 2 放在第三个位置。

最后, 3 2 1
如果没有比当前的排列更大的排列了(如 3 2 1这样最后的排列),就返回最小的排列(比如 1 2 3)

要求修改只能在原数组上进行,不能使用额外的数组。
下面是一些例子,左边是输入的数据,右边是输出的数据。

1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

2. 解题思路

这个题我最初的想法是,比较最后面2个元素的大小。如果倒数第一个比倒数第二个还大,那我们就把他们的位置对换一下。否则就直接返回升序排列不就可以了吗。但是很快就证明我这样的想法真是太幼稚了。
输入 1,2,3
输出 1 3 2 是正确的。
但是
输入 1 3 2
输出 1 2 3 就是错误的。当1 3 2 的时候需要把前面的1 换掉,输出 2 1 3.

后来我参考了LeetCode 上面的题解 https://leetcode.com/problems/next-permutation/solution/ ,才解决了这个问题。

首先,我们要弄清楚应该更改哪些元素,确定元素的范围。

根据观察,我们可以发现,从数组的后面向前面扫描,如果 nums[ i ] > nums[ i-1 ],那么从nums[ i - 1 ] 开始向后的元素都要更改,其中也包括 nums[ i - 1 ] 。

然后,我们要弄清楚怎么改这些元素。

1 . 找到升序排列
在从后向前的遍历中,由于 nums[ i ] > nums[ i-1 ]是第一次出现的升序排列,我们可以知道 nums[ i−1 ] 右边的所有数字都已按降序排序。因此,这个 i - 1 的位置就该换别的数字了。换哪个数字好呢?应该换右边所有数字中,刚好比它大的数。

2 . 交换数字
什么是“刚好比它大的数”呢?就是在所有比 nums[ i - 1]大的数中,那个最小的数。由于 nums[ i−1 ] 右边的所有数字都已按降序排序,所以我们需要从后向前遍历,找到第一个比nums[ i - 1] 大的数,不妨就设它为 nums[ j ]吧。找到后,交换nums[ i-1 ]和 nums[ j ]。

3 . 反转数组
交换之后,我们只要将 i-1 右边的数按照升序排列就可以了。由于原本它们就是降序排列的(即使交换了 nums[ i-1 ]和 nums[ j ],它们也是降序排列的),所以这里我们可以偷个懒,直接反转 i-1 右边所有的数就可以完成升序排列了。

实现代码

class Solution {
    public void nextPermutation(int[] nums) {
		int length = nums.length;
		if(length == 1) {
			return;
		}
		
		int ifUpFlag = -1;
		for(int i = length-1 ; i > 0 ; i--) {
			if(nums[i] > nums[i-1]) {
				ifUpFlag = i;
				
				//寻找刚好比nums[i-1]大的数并与nums[i-1]交换
				for(int k = length-1 ; k>=i ; k--) {
					if(nums[k] > nums[i-1]) {
						int temp = nums[k];
						nums[k] = nums[i-1];
						nums[i-1] = temp;
						break;
					}
				}
				break;
			}
		}
		
		//如果没有遇到nums[i] > nums[i-1]的情况
		//说明一直都是降序排列,直接将整个数组反转
		if(ifUpFlag == -1) {
			reverse(0 , length-1, nums);
		}
		//如果遇到了nums[i] > nums[i-1]的情况
		//说明有升序,反转 [i ,length-1]范围内的数组
		else {
			reverse(ifUpFlag , length-1, nums);
		}
		return;
    }
	
	public void reverse(int left, int right, int [] nums) {
		int temp = 0;
		while(left <right) {
			temp = nums[right];
			nums[right] = nums[left];
			nums[left] = temp;
			
			left ++;
			right --;
		}
		return;
	}
}

3. 参考资料

https://leetcode.com/problems/next-permutation/solution/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值