题目描述:
31. 下一个排列
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
示例 4:
输入:nums = [1]
输出:[1]
代码:
class Solution:
def nextPermutation(self, nums: list) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
flag = 0#用来判断是否找到了nums[i]<nums[i+1],如果没有,则说明原排列是降序的,需要将它进行升序排序。
for i in range(n-2, -1, -1):
if nums[i] < nums[i+1]:
flag = 1
for j in range(n-1, i, -1):
if nums[j] > nums[i]:
nums[j], nums[i] = nums[i], nums[j]
t = i + 1
k = n - 1
while t < k:
nums[t], nums[k] = nums[k], nums[t]
t += 1
k -= 1
break
if flag == 1:
break
if flag == 0:
nums.sort()
'''class Solution:
def nextPermutation(self, nums: list) -> None:
i = len(nums) - 2
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
j = len(nums) - 1
while j >= 0 and nums[i] >= nums[j]:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
left, right = i + 1, len(nums) - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1'''
if __name__ == '__main__':
s = Solution()
nums = [3,2,1]
s.nextPermutation(nums)
print(nums)
Ps:注释掉的是官方代码
解题思路:
先举个简单的例子,就用题目的给的示例吧:
[1,2,3]
找他的下一个排列,从数组的末尾开始,看看前边谁比3小,这里是2,所以交换2,3,即得下一个排列。
但是下面这个数组
[2,3,1]
我们先看1,前边没有比它小的,则看下一个3,前边比它小的是2,交换2,3,此时数组变成[3,2,1],并不是他的下一个排列,问题出在交换后,3后边的数组是降序的,需要把它升序后才是下一个排列。这并不是特例,可以证明交换后边的排序一定是降序的。
再看一个例子吧
[1,3,2]
我们交换1,2,数组变成[2,3,1],也需把[3,1]变成[1,3]才是下一个排列。
这是为啥呢,再举个例子吧。
[4,2,0,2,3,2,0]
这个数组,我们从后往前数的话,从0开始,没有比0小的,接下来看2,这时我们能找到比2小的是0(【4,2,0,2,3,2,0】,数组中标黑的两个数),但交换它两是错的,因为我们还有比它小的排列,比如交换[2,3]([4,2,0,2,3,2,0]),所以我们从后往前找小的数时,不是简单的从最后一个数开始,逐个比较,我们找到的这个”小的数“应该是最靠右的,这样才能保证交换后离原排列是最近的。正确的寻找方法是从后往前比较 nums[i] 和 nums[i+1],i从n-2开始(下标从0开始,最后一个数下标是n-1),如果满足nums[i]<nums[i+1],则停止寻找,接下来j从n-1开始,寻找右边第一个比nums[i]大的数,交换nums[i]和nums[j],再把 i 右边降序的数进行升序排列就可以了。
接下来解释一下为什么i右边的数一定是降序的,从我们寻找nums[i]<nums[i+1]可以知道,我们找到的一定是第一个小于右边排列的数,所以i右边一定是降序的。接下来j从后往前寻找,找第一个大于nums[i]的,交换后,nums[i]也满足右边的比它小,左边的比它大(比如[4,2,0,2,3,2,0]这个例子,我们先从后前寻找,第一个满足nums[i]<nums[i+1]的就是【2,3】,于是我们找到了2,可以看到2右边的数组是降序的【3,2,0】,然后我们在从后往前找第一个比2大的数,第一个是3,交换【2,3】,原数组变成[4,2,0,3,2,2,0],可以看到【2,2,0】也是降序的,2右边是【2,0】,左边是【3】,满足右边比它小,左边比它大)。至此,我们就解释了为什么交换后下标i右边的数一定是降序的。
下一个步骤是把下标i后边的数进行升序排序,因为题目要求原地操作,Python的nums.sort()只能进行整个列表的排列,不能进行部分排列,sorted(nums[i:])虽然可以进行部分排列,但他不是原地排列,结果需要赋值给一个新的列表。所以这两个方法都行不通,我们需要自己排列。因为i后边的数组是降序的,所以我们可以使用双指针的方法,左边从i+1开始,右边从n-1开始,交换两边的数,两个指针往中间移动,直到左边指针大于右边指针,结束交换。也举个例子吧,奇数个的数组比如【5,4,3,2,1】,交换【5,1】,【4,2】,左边指针等于右边指针了,退出循环,数组变成【1,2,3,4,5】,偶数的比如【4,3,2,1】,交换【4,1】,【3,2】,左边指针大于右边指针了,退出循环,数组变成【1,2,3,4】。
时间复杂度:O(N),其中 N为给定序列的长度。我们至多只需要扫描两次序列,以及进行一次反转操作。
空间复杂度:O(1),只需要常数的空间存放若干变量。