LeetCode第31题思悟——下一个排列(next-permutation)
知识点预告
- 简洁的变量命名;
- 重复代码的抽离;
- 对问题的理解和分析;
题目要求
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
来源:力扣(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;说明从右往左,一定会出现“断层”;再来考虑“下一个最大”;要求下一个,说明修改的数字位数越少越好;那么怎么得到下一个最大呢?找到第一个断层点(实现下一个),然后将断层点拔高,同时后缀尽量变小(实现下一个最大);
于是,基于上述分析,我们可以找到这样的解题思路:
- 寻找断层点;记做A;其后的序列记做B;也就说,B=B1B2B3的话,B3<B2<B1,且B1>A;
- 在B中寻找到第一个大于A的数,记做BX;
- 交换BX和A,实现断层点拔高;
- 将BX后面的数字重新排列,成最小值;
- 得到下一个最小值;
举个栗子:876535421;
- 找到断层点:1245显然是满足规律A的,也就是说3就是我们的断层点A;B=5421;
- B中找到第一个大于3的数,即BX=4;
- 交换后成45321;
- 将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;
}
差异分析
大家看到优秀解法后,觉得差异在哪里呢?
- 简洁:优秀解法明显使用了更为简洁的变量命名和更少的变量;相比之下,我的解法中变量就很冗余了;不过这也是因为我担心简洁命名后,自己转眼忘记了变量是啥意义;(嗯,换句话说,水平的提升空间很大,继续努力);
- 抽离:公共代码段抽离成函数;降低重复代码;很好的编程习惯;
知识点小结
- 简洁的变量命名;
- 重复代码的抽离;
- 对问题的理解和分析;
PS:LeetCode专栏传送门 欢迎关注,你不会后悔关注这个专栏的