剑指offer27_字符串的排列_题解
字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
示例1
输入
"ab"
返回值
["ab","ba"]
分析
方案一:回溯搜索+剪枝
全排列的基础上增加了序列中的元素可重复这一条件,但要求:返回的结果又不能有重复元素。
思路是:在遍历的过程中,一边遍历一遍检测,在一定会产生重复结果集的地方剪枝。
对比图中标注 ① 和 ② 的地方。相同点是:这一次搜索的起点和上一次搜索的起点一样。不同点是:
- 标注 ① 的地方上一次搜索的相同的数刚刚被撤销;
- 标注 ② 的地方上一次搜索的相同的数刚刚被使用。
代码
/**
时间复杂度:O(N×N!)
这里 N 为数组的长度。
空间复杂度:O(N×N!)
**/
class Solution
{
public:
void dfs(string s, int len, int depth, vector<bool> &used, string path, vector<string> &res)
{
if (depth == len)
{
res.emplace_back(path);
return;
}
for (int i = 0; i < len; i++)
{
if (used[i])
{
continue;
}
//剪枝条件:i>0是为了保证s[i-1]有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if (i > 0 && s[i] == s[i - 1] && !used[i - 1])
{
continue;
}
path.push_back(s[i]);
used[i] = true;
dfs(s, len, depth + 1, used, path, res);
used[i] = false;
path.pop_back();
}
}
vector<string> Permutation(string str)
{
vector<string> res;
int len = str.length();
// 特判
if (len == 0)
{
return res;
}
vector<bool> used(len, false);//访问数组
string path = "";
// 排序
sort(str.begin(), str.end());
// 回溯
dfs(str, len, 0, used, path, res);
return res;
}
};
方案二:递归交换+剪枝+set去重
根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符( n-1 种情况)、... 、最后固定第 n 位字符( 1 种情况)。
重复方案与剪枝: 当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。
代码
/**
时间复杂度:O(N!): N为字符串s的长度,方案数为N*(N-1)*(N-2)...*2*1=N!
空间复杂度:O(N^2):全排利的递归深度为N,系统累计使用栈空间大小为O(N);递归中辅助set累计存储的字符数量最多为N+(N-1)+...+2+1=(N+1)N/2,即占用O(N^2)的额外空间
**/
class Solution
{
public:
vector<string> ans;
vector<char> c;
void dfs(int x)
{
if (x == c.size() - 1)
{
string res(c.begin(), c.end()); // 添加排列方案
ans.push_back(res);
return;
}
set<char> st;
for (int i = x; i < c.size(); i++)
{
if (st.count(c[i]))
continue;
st.insert(c[i]);
swap(c[i], c[x]); // 交换,将 c[i] 固定在第 x 位
dfs(x + 1); // 开启固定第 x + 1 位字符
swap(c[i], c[x]); // 恢复交换
}
}
vector<string> Permutation(string str)
{
for (int i = 0; i < str.size(); i++)
{
c.push_back(str[i]);
}
dfs(0);
sort(ans.begin(), ans.end());
return ans;
}
};
方案三:递归交换+set自动去重
将所有排列方案插入set集合进行自动去重
代码
class Solution
{
public:
void dfs(string s, int start, set<string> &visited)
{
if (start == s.size())
{
visited.insert(s);
return;
}
for (int i = start; i < s.size(); i++)
{
//做选择
swap(s[i], s[start]);
dfs(s, start + 1, visited);
//交换回去
swap(s[i], s[start]);
}
}
vector<string> Permutation(string str)
{
set<string> visited;
dfs(str, 0, visited);
return vector<string>(visited.begin(), visited.end());
}
};