31. 下一個排列
題目
實現獲取下一個排列的函式,演算法需要將給定數字序列重新排列成字典序中下一個更大的排列。
如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。
必須原地修改,只允許使用額外常數空間。
以下是一些例子,輸入位於左側列,其相應輸出位於右側列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
解題思路
思路:迭代
首先先理解題意,題目中要求【將給定數字序列重新排列成字典序中下一個更大的排列。如果不存在,則將數字重新排列稱最小的排列(即升序排列)】
在這裡,可能直接從文字上面來看,不太不能夠理解是什麼意思,那麼結合例子來看,先看
1,2,3 → 1,3,2
1,1,5 → 1,5,1
在這裡,你可以理解為,要將數字 123 變為下一個更大的數字,132。115 也同理。
而下面這個例子就是表示不存在更大的排列:
3,2,1 → 1,2,3
321 已經是最大的了,那麼就將其排列為最小的排列(升序排列),得到結果 123。
其實從上面的例子中,多多少少也能夠看出來,在這裡其實是從後面開始找,當找到相鄰升序的兩個數字,在這裡將它們進行交換,這樣就能夠得到更大的排列。
其實這裡還有一部分的內容,在題目中是比較難看出來的,題目中【下一個】這個概念,其實要找到的是變化前後的排列,增加的幅度儘可能小。比如,下面的例子:
1,2,3,4,5 → 1,2,3,5,4
1,2,3,5,4 → 1,2,4,3,5
第一個示例,根據上面觀察所得,即是將 4 和 5 進行替換,得到更大的排列,12354。
後面的示例中 12354,得到排列的結果 12435。在這裡,交換的是 3 和 4,這裡其實交換的數字是儘可能小的大數和前面的小數,所以並不是 3 和 5 進行交換,而交換後的所有數還需要重置升序。所得出的結果是 12435,而不是 12453。
這就是關於題意的簡單分析,下面看如何實現演算法:
- 首先需要明確的是從後面往前查詢第一個相鄰升序的兩個元素所在的位置(i,j),滿足 A[i] < A[j]。而且,從位置 j 往後的元素是降序的。
- 在 j 往後的元素中,同樣是從後往前找,找到第一個滿足 A[i] < A[k] 的元素,將其兩者進行交換。
- 前面說了, 從 j 往後一定是降序的,那麼交換以後肯定也是降序的(因為找到 A[k] 是第一個比 A[i] 大的數字,由於是從後往前找,k所在位置左邊的數字勢必比當前 i 所在位置的數字大,而右邊的數字也就比其小),這個時候,要將這部分降序,逆轉為升序,這樣才能保證排列前後儘可能小的增幅。
- 考慮特殊的情況,也就是整個排列是降序,即是最大的的排列時,執行上面所說逆轉為升序即可。
具體的程式碼實現如下。
程式碼實現
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
if len(nums) < 2:
return
n = len(nums)
# 從陣列右往前進行遍歷,查詢相鄰升序元素
i = n - 2
j = n - 1
while i > 0 and nums[i] >= nums[j]:
i -= 1
j -= 1
# 這裡有一種情況,就是迴圈結束後,i 為 0 且索引 0 位置的數是最大的情況
# 那這裡就表示排列就是最大的排列,將其逆轉升序
if i == 0 and nums[i]==max(nums):
nums.reverse()
else:
# 當找到相鄰的升序元素時
# 再次從後往前找到一個比 nums[i] 大但相比其他元素儘可能小的數
k = n - 1
while nums[i] >= nums[k]:
k -= 1
# 交換兩個元素
nums[i], nums[k] = nums[k], nums[i]
# 現在 j 到後面的元素是降序的,這裡要將其升序
length = n - j + 1
for x in range(length // 2):
nums[j+x], nums[n-1-x] = nums[n-1-x], nums[j+x]
實現結果
以上就是關於《31. 下一個排列》問題的分析及具體實現演算法的主要內容。
歡迎關注微信公眾號《書所集錄》