题目描述:给定一个若干整数的排列,给出按正数大小进行字典序从小到大排序后的下一个排列。如果没有下一个排列,则输出字典序最小的序列。
样例:左边是原始排列,右边是对应的下一个排列。
1,2,3 → 1,3,2
3,2,1 → 1,2,31,1,5 → 1,5,1
先解释一下“字典序”。官方的准确定义我就不说了,自己可以去查。我只说说针对这类编程题,我们应该怎么理解。
一个序列,这里,为了简单,就当做是正整数的序列吧,它有自己的排列方式,比较有规则的是两种:
1. 升序:由小到大,依次排列,如1, 2, 3, 4
2. 降序:由大到小,依次排列,如4, 3, 2, 1
“字典序”中,我们将升序记为第一个排列,而将降序记为最后一个排列,显然,对于上面这个有4个元素的数组来说,一共有4!个排列,也就是说,除了我上面写出来的两个,还有22个排列。这些排列的顺序,就是“字典序”。
字典序的规律是这样的:首先,我先给两个概念:
1. 高位:越靠左越高
2. 低位:越靠右越低
也就是说,一个排列中,如果元素A在元素B的左边,则说A是高位,B是低位。而“字典序”就是一个不断增大高位数值,减小低位数值的过程。比如排列1, 2, 3三个整数,字典序的排列是这样的:
(1) 1, 2, 3
(2) 1, 3, 2
(3) 2, 1, 3
(4) 2, 3, 1
(5) 3, 1, 2
(6) 3, 2, 1
从(1)到(6)就是不断增加高位数值的过程。
那么怎么找下一个排列呢?我们可以这样考虑:
1. 先找到需要改变的高位:从右向左扫描排列,若一直满足nums[i] > nums[i - 1],则说明这些元素是满足高位大于低位的,不需操作,直到找到nums[i] < nums[i - 1],找到高位比低位小的了,而且是“最低”的高位,这个位置就是我们需要做交换操作的。比如:6, 8, 7, 4, 3, 2 当中的8(发现6 < 8)
2. 第二步找要和这个高位交换的低位:原则是尽量寻找只比这个高位“大一点”的低位,因为只是下一个排列。因为在这个高位右边的数组满足从右往左递增,所以,我们重新从右边起扫描,找到第一个比这个高位大的元素。比如:6, 8, 7, 4, 3, 2 中的7.
3. 交换高位与低位,使得高位变大:比如6, 8, 7, 4, 3, 2 -> 7, 8, 6, 4, 3, 2
4. 此时,不论交换后的高位后面的元素是如何排列的,都肯定比之前的排列靠后了。因为只是下一个排列,所以我们现在尽量要现在的这个排列“靠前”,怎么做呢,就是按升序排列高位后面的元素,比如:按升序排列此时7后面的元素。因为此时高位后面的元素一定是从左往右,从大到小,所以,也相当于是翻转这一部分的数组。比如:7, 8, 6, 4, 3, 2 -> 7, 2, 3, 4, 6, 8
整个步骤完成,很多博客只是给了大家方法,对原理的解释实在是有些含糊,我在这里希望给大家说明白了。
代码如下:
class Solution:
# @param nums: a list of integer
# @return: return nothing (void), do not return anything, modify nums in-place instead
def nextPermutation(self, nums):
n = len(nums)
if n == 0 or n == 1:
return
right_index = n - 1
pivot = 0
# 从右边起找第一个(i - 1, i),使得nums[i] > nums[i - 1]
while right_index >= 0:
if right_index - 1 >= 0 and nums[right_index] > nums[right_index - 1]:
# 将找到的i - 1记为pivot
pivot = right_index - 1
break
elif right_index == 0:
pivot = right_index - 1
break
else:
right_index -= 1
# 若pivot < 0,则说明这是最后一个排列了,就需要将nums改为最小的排列
if pivot < 0:
nums.sort()
else:
right_index = n - 1
# 从右边起,找第一个大于nums[pivot]的数,并将这个数与nums[pivot]交换
while right_index >= 0:
if nums[right_index] > nums[pivot]:
nums[right_index], nums[pivot] = nums[pivot], nums[right_index]
break
else:
right_index -= 1
# 将此时pivot之后的部分数组翻转(其实也就是重新排序)
left = pivot + 1
right = n - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
# write your code here
lintcode上还有一道“下一个排列”的问题,只不过元素可重复,其实原理,甚至连代码都跟这道题一样,我就不说了。