本文始發於我的公衆號:TechFlow,原創不易,求個關注web
今天是LeetCode的26篇文章,咱們來實戰一下全排列問題。算法
在以前的文章當中,咱們講過八皇后、回溯法,也提到了全排列,可是畢竟沒有真正寫過。今天的LeetCode46題正是讓咱們生成給定元素的全排列。數組
題意很簡單,只有一句話,給定一個沒有重複元素的序列,讓咱們返回這個序列全部的全排列,而且咱們不須要考慮這些排列的順序。app
咱們在以前的文章當中分析過,全排列問題,能夠當作是搜索問題,從而近似成八皇后問題。在八皇后問題當中,咱們枚舉的是棋盤的每一行當中的皇后放置的位置,而全排列其實也同樣,咱們要枚舉每個元素放置的位置。不過八皇后當中要求皇后除了不能同行同列以外還不能同對角線,而咱們排列元素能夠忽略這個要求。也就是說咱們把每一行皇后放置的列號當作是每一個元素擺放的位置,而且忽略同對角線的限制的話,那麼八皇后問題和全排列問題就徹底同樣了。編輯器
若是還不理解,能夠參考一下下圖,咱們給皇后編號,把皇后一樣當作是序列當中的元素,那麼八皇后的擺放位置恰好能夠映射成一種排列。映射的方式很是簡單,就是咱們忽略行的信息,依次記錄下皇后擺放的列號。spa
若是你能想通這兩個看似徹底不一樣的問題當中的類似之處,說明你對搜索問題的理解已經有些入門了。3d
思路清楚了,總之咱們要枚舉皇后擺放的狀態。你能夠按順序遍歷位置,而後枚舉各個位置上放置的皇后,也能夠順序遍歷皇后,枚舉當前皇后能夠放置的位置。二者是等價的,你能夠根據本身的理解進行操做。code
通常來講我喜歡遍歷位置,枚舉皇后。由於會引發衝突的是皇后,而不是位置。咱們每每要判斷皇后之間的關係以及皇后的狀態,因此咱們枚舉皇后會比較貼合思路。cdn
因此咱們把以前八皇后的代碼拿過來稍做修改便可,爲了放置一個皇后重複放置在多個位置,咱們須要存儲皇后的狀態,即有沒有放置過。通常競賽當中這種標記的變量稱爲flag,若是標記多個那就是flag數組。更多細節咱們來看代碼:blog
class Solution:
def dfs(self, nums, n, i, cur, ret, flag):
if i == n:
ret.append(cur.copy())
return
for p in range(n):
# 遍歷全部元素
# 若是p元素已經放置過了,跳過
if flag[p]:
continue
# 當前位置放置p
cur.append(nums[p])
# flag[p]置爲True
flag[p] = True
# 遞歸
self.dfs(nums, n, i+1, cur, ret, flag)
# 回溯
cur.pop()
flag[p] = False
def permute(self, nums: List[int]) -> List[List[int]]:
ret = []
n = len(nums)
# 記錄元素i有沒有放置過
flag = [False for _ in range(n)]
self.dfs(nums, n, 0, [], ret, flag)
return ret
複製代碼
代碼很短,細節也很少,只要理解了咱們是按照順序遍歷位置,而後對於每個位置遍歷能夠放置的元素,而後遞歸回溯便可。基本上能夠說是模板題,若是理解有難度的話,能夠看一下以前詳解八皇后問題的文章:
LeetCode 31:遞歸、回溯、八皇后、全排列一篇文章全講清楚
回溯法是這個問題的標準解法,那麼這題還有沒有其餘方法呢?
實際上是有的,也不難,在LeetCode31題的文章,也就是上面那個連接的文章當中咱們解決了一個叫作下一個排列的問題。在這道題當中,咱們給定一個序列,要求返回在它全部的全排列當中恰好字典序比它大1的排列,這個方法稱爲next_permutation。
關於next_permutation的計算方法也在連接裏,若是有忘記的或者是最近關注的能夠點下連接回顧一下,計算方法是徹底同樣的,我就再也不重複了。
LeetCode 31:遞歸、回溯、八皇后、全排列一篇文章全講清楚
若是還記得這道題的話就好辦了,咱們使用它很容易解出當前的問題。由於咱們只須要得到給定序列的最小排列,而後不停地調用這個方法就行了,直到沒有更大的序列退出便可。從最小的序列一直獲取到最大的,固然就是全排列了。
在LeetCode31題當中,這是一個inplace的方法,沒有返回值。而且當序列達到最大的時候,會自動再從最小的開始。咱們須要稍稍修改一下,加上一個返回值,表示當前的序列是不是最大的。若是序列達到最大,說明咱們能夠不用繼續往下尋找了,咱們return一個True,表示能夠退出了,不然咱們return False,表示還有其餘結果。
本質上咱們是從最小的排列開始,不停地用一個叫作get_next的方法獲取比當前序列大的下一個序列,當沒有更大的序列的時候,說明咱們已經得到了全部的排列,那麼直接返回結果便可。若是忽略get_next當中的邏輯,這個代碼其實只有幾行:
其實這是一個取巧的辦法,利用以前的思路咱們徹底不用思考,幾乎能夠無腦獲得答案。可是從另一個角度來講,這也是算法的魅力,畢竟通往終點的路每每不止一條。
最後咱們來看下代碼,若是你不懂怎麼算next_permutation光看註釋是很難看懂的,劃到上面的連接看看吧。
class Solution:
def get_next(self, nums: List[int]):
"""
Do not return anything, modify nums in-place instead.
"""
# 長度
n = len(nums)
# 記錄圖中i-1的位置
pos = n - 1
for i in range(n-1, 0, -1):
# 若是降序破壞,說明找到了i
if nums[i] > nums[i-1]:
pos = i-1
break
for i in range(n-1, pos, -1):
# 從最後開始找大於pos位置的
if nums[i] > nums[pos]:
# 先交換元素,在進行翻轉
nums[i], nums[pos] = nums[pos], nums[i]
# 翻轉[pos+1, n]區間
nums[pos+1:] = nums[n:pos:-1]
return False
return True
def permute(self, nums: List[int]) -> List[List[int]]:
ret = []
# 從小到大排序,得到最小排列
nums = sorted(nums)
ret.append(nums.copy())
# 若是還有下一個排列則繼續調用
while not self.get_next(nums):
# 要.copy()是由於Python中存儲的引用,若是不加copy
# 會致使當nums發生變化以後,ret中存儲的數據也會變化
ret.append(nums.copy())
return ret
複製代碼
今天的問題並不難,只是Medium難度,而且題目的題意仍是以前見過的,主要是給你們加深一下回溯算法的映像用的,沒什麼太多的新內容。
文章的內容就是這些,若是以爲有所收穫,請順手點個關注或者轉發吧,大家的舉手之勞對我來講很重要。