LeetCode第31题思悟——下一个排列(next-permutation)

本文深入解析LeetCode第31题“下一个排列”的算法实现,通过分析“下一个最大”概念,提出寻找断层点、交换元素及重排后缀的策略,提供简洁变量命名和代码抽离的优秀解法。

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

LeetCode第31题思悟——下一个排列(next-permutation)

知识点预告

  1. 简洁的变量命名;
  2. 重复代码的抽离;
  3. 对问题的理解和分析;

题目要求

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

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的思路

这道题的关键在于理解“下一个最大”。

我们考虑一下排列什么时候出现最大:当然是越大的数字在越高的位置上啦;也就是说从右往左,数字逐渐变大(规律A);基于此,我们再理解一下什么时候出现更大?要更大,说明此时还不够大;不够大,说明数字当前不满足A;说明从右往左,一定会出现“断层”;再来考虑“下一个最大”;要求下一个,说明修改的数字位数越少越好;那么怎么得到下一个最大呢?找到第一个断层点(实现下一个),然后将断层点拔高,同时后缀尽量变小(实现下一个最大);

于是,基于上述分析,我们可以找到这样的解题思路:

  1. 寻找断层点;记做A;其后的序列记做B;也就说,B=B1B2B3的话,B3<B2<B1,且B1>A;
  2. 在B中寻找到第一个大于A的数,记做BX;
  3. 交换BX和A,实现断层点拔高;
  4. 将BX后面的数字重新排列,成最小值;
  5. 得到下一个最小值;

举个栗子:876535421;

  1. 找到断层点:1245显然是满足规律A的,也就是说3就是我们的断层点A;B=5421;
  2. B中找到第一个大于3的数,即BX=4;
  3. 交换后成45321;
  4. 将5321重新排列成最小:1235;得到41235;
public void nextPermutation(int[] nums) {
	int length=nums.length;
	if(length<=1){
		return;
	}
	int temp;
	int borderFinder=length-1;
	int nextCheckIndex=borderFinder-1;
	int exchangeFinder;
	int right=length-1;
	while(nextCheckIndex>=0&&nums[nextCheckIndex]>=nums[borderFinder]){
		borderFinder--;
		nextCheckIndex--;
	}
	if(nextCheckIndex>=0){//存在最小的较大值
		exchangeFinder=length-1;
		temp=nums[nextCheckIndex];
		while(exchangeFinder>=borderFinder&&nums[exchangeFinder]<=temp){
			exchangeFinder--;
		}
		//交换
		nums[nextCheckIndex]=nums[exchangeFinder];
		nums[exchangeFinder]=temp;
	}
	//两两交换length-1和borderFinder之间的数字
	while(borderFinder<right){
		temp=nums[right];
		nums[right]=nums[borderFinder];
		nums[borderFinder]=temp;
		right--;
		borderFinder++;
	}
}

优秀解法

//解法A
public void nextPermutation(int[] nums) {
	int i = nums.length - 2;
	while (i >= 0 && nums[i + 1] <= nums[i]) {
		i--;
	}
	if (i >= 0) {
		int j = nums.length - 1;
		while (j >= 0 && nums[j] <= nums[i]) {
			j--;
		}
		swap(nums, i, j);
	}
	reverse(nums, i + 1);
}
private void reverse(int[] nums, int start) {
	int i = start, j = nums.length - 1;
	while (i < j) {
		swap(nums, i, j);
		i++;
		j--;
	}
}
private void swap(int[] nums, int i, int j) {
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}

差异分析

大家看到优秀解法后,觉得差异在哪里呢?

  1. 简洁:优秀解法明显使用了更为简洁的变量命名和更少的变量;相比之下,我的解法中变量就很冗余了;不过这也是因为我担心简洁命名后,自己转眼忘记了变量是啥意义;(嗯,换句话说,水平的提升空间很大,继续努力);
  2. 抽离:公共代码段抽离成函数;降低重复代码;很好的编程习惯;

知识点小结

  1. 简洁的变量命名;
  2. 重复代码的抽离;
  3. 对问题的理解和分析;

PS:LeetCode专栏传送门 欢迎关注,你不会后悔关注这个专栏的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值