劍指offer_38_字串的全排列
阿新 • • 發佈:2020-07-17
字串的全排列
題目連結:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
題目描述:輸入一個字串,打印出該字串中字元的所有排列。你可以以任意順序返回這個字串陣列,但裡面不能有重複元素。
示例:
輸入:s = "abc"
輸出:["abc","acb","bac","bca","cab","cba"]
限制:1 <= s 的長度 <= 8
題目解析
題目解析內容參考自題解中krahets
排列方案的數量:全排列,對於一個長度為 n 的字串(假設字元互不重複),其排列共有 n * (n - 1) * (n - 2) ... * 2 * 1 種方案
排列方案的生成方法:根據字串排列的特點,可將字串分為兩部分。
- 首字母
- 首字母之後的字母
選中第一個字母后,將剩下的字母仍按照上述進行拆分。由此,可以得出一個遞迴的思想。
根據上述流程可以採用深度優先搜尋的方式。見下圖。
重複方案與剪枝: 當字串存在重複字元時,排列方案中也存在重複方案。為排除重複方案,需在固定某位字元時,保證 “每種字元只在此位固定一次” ,即遇到重複字元時不交換,直接跳過。從 DFS 角度看,此操作稱為 “剪枝” 。
遞迴解析:
- 終止條件: 當 x = len(c) - 1 時,代表所有位已固定(最後一位只有 1 種情況),則將當前組合
c
轉化為字串並加入res
,並返回; - 遞推引數: 當前固定位
x
; - 遞推工作: 初始化一個 Set ,用於排除重複的字元;將第 x 位字元與 i∈[x,len(c)] 字元分別交換,並進入下層遞迴;
- 剪枝: 若 c[i] 在 Set 中,代表其是重複字元,因此“剪枝”;
- 將 c[i] 加入 Set ,以便之後遇到重複字元時剪枝;
- 固定字元: 將字元 c[i] 和 c[x] 交換,即固定 c[i] 為當前位字元;
- 開啟下層遞迴: 呼叫 dfs(x + 1) ,即開始固定第 x + 1 個字元;
- 還原交換: 將字元 c[i] 和 c[x] 交換(還原之前的交換);
以上總結就是一句話,在已經變成列表的字串,調換各個引數的位置,從而達到,不同排序的列表。
複雜度分析:
- 時間複雜度 O(N!) : N 為字串 s 的長度;時間複雜度和字串排列的方案數成線性關係,方案數為N×(N−1)×(N−2)…×2×1 ,因此複雜度為 O(N!) 。
- 空間複雜度 O(N^2): 全排列的遞迴深度為 N ,系統累計使用棧空間大小為 O(N) ;遞迴中輔助 Set 累計儲存的字元數量最多為 N + (N-1) + ... + 2 + 1 = (N+1)N/2 ,即佔用 O(N^2)的額外空間。
程式碼
class Solution:
def permutation(self, s: str) -> List[str]:
c, res = list(s), []
def dfs(x):
if x == len(c) - 1:
res.append(''.join(c)) # 新增排列方案
return
dic = set()
for i in range(x, len(c)):
if c[i] in dic: continue # 重複,因此剪枝
dic.add(c[i])
c[i], c[x] = c[x], c[i] # 交換,將 c[i] 固定在第 x 位
dfs(x + 1) # 開啟固定第 x + 1 位字元
c[i], c[x] = c[x], c[i] # 恢復交換
dfs(0)
return res