字典序也就是从头开始,依次比较每个字符在ASCII码中的排位,例如a的排位在b之前,1的排位在3之前,如果字符相同,就继续往后比较。显然,一个只有数字的list要按字典序排列,以[1, 2, 3, 4]为例,应该为:
[1, 2, 3, 4] -> [1, 2, 4, 3] -> [1, 3, 2, 4] -> [1, 3, 4, 2] -> [1, 4, 2, 3] -> [1, 4, 3, 2] -> [2, 1, 3, 4] -> [2, 1, 4, 3] -> [2, 3, 1, 4] ->[…]
从实例总结规律,可以发现以下几点:
-
如果list中有“小,大”的组合,那要先找到最靠右的较小数nums[i],再找到其右侧值最小的较大数nums[j],将他们交换,例如: [1, 2, 3, 4]-> [1, 2, 4, 3];
-
又如: [1, 3, 4, 2] -> [1, 4, 2, 3],但是这里会注意到3和4交换后,3又被换到了最后一位,这是因为交换后原先3的位置往后所有数字还要重新升序排序,确保这是最靠近原序列的下一个排列,否则,这就不是当前最小的下一个排列。[1, 4, 3, 2] -> [2, 1, 3, 4]也是同理,1与2互换后,[2, 4, 3, 1]还要将后三位的[4, 3, 1]重新升序排列,得到最终结果[2, 1, 3, 4]。
-
升序排列可以用双指针直接一趟交换实现,因为较小数和较大数交换后,右侧序列还是降序排列( 因为nums[j-1] <= nums[i] < nums[j] )。
-
假如序列是一直降序的,也就不存在“小,大”的组合,这时直接将整个序列升序排列就可以了。
-
在找i, j时,i是最靠右的较小数的位置,所以可以从右往左找“小,大”的组合,这样找到的第一个较小数就是最靠右的;而且这样可以确定较小数i之后的数都是降序排列的,那要找其中最小的较大数,也是从右往左找比nums[i]大的数,遇到到的第一个就是最小的较大数。
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# e.g. 1 3 5 7 8 9 6 4 -------> 1 3 5 7 9 4 6 8
# 先找最后一个“小->大”的数对,将其交换,换之后的部分应该再调整为递增的
l = len(nums)
a, b = -1, -1
# 从右往左找到最靠右的较小值
for i in range(l-1, 0, -1):
if nums[i] > nums[i-1]:
a = i-1
break
# 在较小值右边的序列中,从右往左找到最小的较大值
for j in range(l-1, a, -1):
if nums[j] > nums[a]:
b = j
break
# 存在“小->大”的数对,交换较小值和较大值
if a>=0 and b>=0:
temp = nums[a]
nums[a] = nums[b]
nums[b] = temp
# 原来的较小值右侧的数字要重新升序排列
p,q = a+1, l-1
while p<q:
temp = nums[p]
nums[p] = nums[q]
nums[q] = temp
p += 1
q -= 1
return nums