1. 程式人生 > 實用技巧 >劍指offer_38_字串的全排列

劍指offer_38_字串的全排列

字串的全排列

題目連結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 角度看,此操作稱為 “剪枝” 。

遞迴解析:

  1. 終止條件: 當 x = len(c) - 1 時,代表所有位已固定(最後一位只有 1 種情況),則將當前組合 c 轉化為字串並加入 res,並返回;
  2. 遞推引數: 當前固定位 x
  3. 遞推工作: 初始化一個 Set ,用於排除重複的字元;將第 x 位字元與 i∈[x,len(c)] 字元分別交換,並進入下層遞迴;
    1. 剪枝: 若 c[i] 在 Set 中,代表其是重複字元,因此“剪枝”;
    2. 將 c[i] 加入 Set ,以便之後遇到重複字元時剪枝;
    3. 固定字元: 將字元 c[i] 和 c[x] 交換,即固定 c[i] 為當前位字元;
    4. 開啟下層遞迴: 呼叫 dfs(x + 1) ,即開始固定第 x + 1 個字元;
    5. 還原交換: 將字元 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