题目信息
-
题目:剑指 Offer 38. 字符串的排列
-
时间: 2020-09-03
-
题目链接:Leetcode
-
tag:深度优先搜索 回溯法
-
难易程度:中等
-
题目描述:
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
1 | 输入:s = "abc" |
提示
1 | 1 <= s.length() <= 8 |
解题思路
本题难点
对于一个长度为 n 的字符串(假设字符互不重复),其排列共有 n×(n−1)×(n−2)…×2×1 种方案。要求返回的结果又不能有重复元素。
回溯法 :一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解的话(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。
具体思路
这个问题可以看作有 n 个排列成一行的空格,我们需要从左往右依此填入题目给定的 n 个字符,每个字符只能使用一次。
定义chars[]为输入字符串的字符数组,递归函数 backtracking表示从左往右填到第 i 个位置,当前字符串排列为 s。 那么整个递归函数分为两个情况:
- 如果 s.length()==chars.length,说明我们已经填完了 n 个位置(注意下标从 0 开始),找到了一个可行的解,我们将 s 放入答案数组中,递归结束。
- 如果 s.length()<chars.length,我们要考虑这第 i 个位置我们要填哪个字符。根据题目要求我们肯定不能填已经填过的字符,因此很容易想到的一个处理手段是我们定义一个标记数组 hasUsed[] 来标记已经填过的数,那么在填第 i 个数的时候我们遍历题目给定的 n 个字符。
- 如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数backtrack。搜索回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。
在一定会产生重复结果集的地方剪枝。
代码
1 | class Solution { |
复杂度分析:
- 时间复杂度 O(N!) : N 为字符串 s 的长度;时间复杂度和字符串排列的方案数成线性关系,方案数为 N×(N−1)×(N−2)…×2×1 ,因此复杂度为 O(N!) 。
- 空间复杂度 O(N) : 其中 n 为序列的长度。除答案数组以外,递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,这里可知递归调用深度为 O(n)。
其他优秀解答
解题思路
-
排列方案的生成方法:根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符( n−1 种情况)、… 、最后固定第 n 位字符( 1 种情况)。
-
重复方案与剪枝:
当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。
代码
1 | public class Exam38_permutation { |