剑指offer-38

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

1
2
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

官方题解

对于一个长度为 $n$ 的字符串(假设字符互不重复),其排列方案数共有:
$$
n \times ( n - 1 ) \times ( n - 2 ) \ldots \times 2 \times 1
$$
排列方案的生成:

根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符( n-1 种情况)、… 、最后固定第 n 位字符( 1 种情况)。

image-20210617192225953

重复排列方案与剪枝:

当字符串存在重复字符时,排列方案中也存在重复的排列方案。

为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过

从 DFS 角度看,此操作称为 “剪枝” 。

image-20210617192414005

递归解析

  1. 终止条件:x = len(c) - 1 时,代表所有位已固定(最后一位只有 1 种情况),则将当前组合 c 转化为字符串并加入 res ,并返回;
  2. 递推参数: 当前固定位 x
  3. 递推工作: 初始化一个 Set ,用于排除重复的字符;将第 x 位字符与$i \in [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] 交换(还原之前的交换);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def permutation(self, s: str) -> List[str]:
c = list(s)
res = []

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)
c[i], c[x] = c[x], c[i] # 恢复交换

dfs(0)
return res
作者

bd160jbgm

发布于

2021-06-17

更新于

2021-06-17

许可协议