Leetcode-31 下一个序列
一、题目
- 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
- 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
- 必须原地修改,只允许使用额外常数空间。
- 链接:https://leetcode-cn.com/problems/next-permutation
二、算法描述
1.二次遍历
此段复制于官方给出的算法描述:
注意到下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:
- 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
- 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。
简言之,首先我们的目标序列要比当前的序列大,将低位的大数(指大于要交换的高位数)与高位的数字交换即可;同时又要使增大的幅值是最小的:
- 对低位的大数【较大数】,①需要满足数值尽量小,②但需保证大于要交换的高位数.
- 对于高位的数字【较小数】,③则需要满足位值尽量小.
以排列 [4,5,2,6,3,1]为例:
我们能找到的符合条件的一对「较小数」与「较大数」的组合为 2 与 3,满足「较小数」尽量靠右,而「较大数」尽可能小。
当我们完成交换后排列变为 [4,5,3,6,2,1],此时我们可以重排「较小数」右边的序列,序列变为 [4,5,3,1,2,6]。
对于长度为 n 的排列 a:
- 首先从后向前( ③)查找第一个顺序对 (i,i+1),满足 a[i] < a[i+1](②)。这样「较小数」即为 a[i]。此时 [i+1,n)必然是下降序列。 [4,5, 2,6,3,1]
- 如果找到了顺序对,那么在区间 [i+1,n) 中从后向前查找第一个元素 jj 满足 a[i] <a[j](①)。这样「较大数」即为 a[j] 。 [4,5,2,6,3,1]
- 交换 a[i] 与 a[j],此时可以证明区间 [i+1,n) 必为降序。我们可以直接使用双指针反转区间 [i+1,n)使其变为升序,而无需对该区间进行排序。
2.根据索引位值
参考链接:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-by-powcai/
作者:powcai
Wiki上给出的算法思路:
- 先找出最大的索引 k 满足 nums[k] < nums[k+1],如果不存在,就翻转整个数组;
- 再找出另一个最大索引 L 满足 nums[L] > nums[k];
- 交换 nums[L] 和 nums[k];
- 最后翻转 nums[k+1:]。
依然以排列 [4,5,2,6,3,1]为例:
- 最大的索引 k :nums[2] < nums[3] k = 2
- 最大的索引 L :nums[4] > nums [2] L = 4
- [4,5,2,6,3,1] ------> [4,5,3,6,2,1]
- [4,5,3,6,2,1] ------> [4,5,3,1,2,6]
然而对于该算法逻辑层面的思考,并没有给出,在此附上个人理解,仅供参考。如有错误,还请指出.
对于一组给定的数列,如【1,2,3,4,5】;它的字典序依次为:
【1,2,3,5,4】,【1,2,4,3,5】,【1,2,4,5,3】…
可以看出最小值首先在最ZUO边即最高位,次小值在次高位,依次类推;
交换发生后,历程应该是大值从低位逐步攀升至高位的过程。最终结果应该呈现为最大值在最ZUO边,最小值在最右边。
每个数字都有自己的宿命,但它们都会到其他的坑踩踩.
交换数值首先从低位发生,从右往ZUO。
言归正传:
1.先找出最大的索引 k 满足 nums[k] < nums[k+1],如果不存在,就翻转整个数组;
此句存在两个条件:
Ⅰ.nums[k] < nums[k+1] ②【较大数】需保证大于要交换的高位数.
Ⅱ.最大的索引 k ③【较小数】需要满足位值尽量小.
综合上述两个条件可以确保【k+1:】是降序。
2.再找出另一个最大索引 L 满足 nums[L] > nums[k]; ①【较大数】需要满足数值尽量小.
可以看到这里所谓的最大索引与方法一的从后向前寻找的目的是一样的,之后的交换与反转也与第一个方法类似,两个方法区别仅在于一个是从右往ZUO寻找,一个是从ZUO往右。
三、代码实现
1.python3
class Solution1:
def nextPermutation(self, nums):
length = len(nums)
leeserIndex = -1
largerIndex = 1
for i in range(length-2,-1,-1):
if nums[i] < nums[i+1] :
leeserIndex = i
break
#找出较小数 从右往ZUO
if leeserIndex == -1 :
nums.reverse()
return nums
#如果没有 说明是整个序列是逆序,翻转即可
else:
for i in range(length-1,leeserIndex,-1):
if nums[i] > nums[leeserIndex]:
largerIndex = i
break
#找出较大数 从右往ZUO
nums[leeserIndex],nums[largerIndex] = nums[largerIndex], nums[leeserIndex]
#交换
ZUO, right = leeserIndex + 1, length - 1
while ZUO < right:
nums[ZUO], nums[right] = nums[right], nums[ZUO]
ZUO += 1
right -= 1
#排序
return nums
# Run time : 36ms Memory consump:14.8MB
class Solution2():
def nextPermutation(self,nums):
length = len(nums)
K = -1
L = 1
for i in range(0,length-1):
if nums[i] < nums[i+1]:
K = i
#找较小数 从ZUO往右
if K == -1:
nums.reverse()
else:
for i in range(K-1,length):
if nums[i] > nums[K]:
L = i
#找较大数 从ZUO往右
nums[K],nums[L] = nums[L],nums[K]
ZUO, right = K + 1, length - 1
while ZUO < right:
nums[ZUO], nums[right] = nums[right], nums[ZUO]
ZUO += 1
right -= 1
#排序
return nums
# Run time : 48ms Memory consump:14.7MB
代码写的不是很精简,可以看出从后往前遍历的运行时间是比较少的,方法一较优。
2.JS
明天更新~~~~~~: