📟作者主页:慢热的陕西人
🌴专栏链接:力扣刷题日记
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
文章目录
牛客热题:字符串的全排列
题目链接
字符串的排列_牛客题霸_牛客网 (nowcoder.com)
方法一:递归|回溯
思路
- 函数
_Per
(递归生成排列):- 该函数使用递归来生成字符串的所有排列。
- 它接受一个
set<string>& hash
用来存储生成的排列结果(避免重复),一个vector<int>& st
用来记录哪些字符已经使用过,一个临时字符串s
记录当前生成的排列,一个引用string& str
是输入的字符串。 - 递归的终止条件是当前生成的字符长度等于输入字符串的长度。
- 在每个递归步骤中,它循环检查哪些字符没有被使用,然后递归生成下一个字符的排列。
- 函数
Permutation
(入口函数):- 这个函数初始化结果容器
res
、状态向量st
和哈希集合hash
。 - 调用
_Per
来生成所有排列。 - 将
hash
的内容复制到res
中返回。
- 这个函数初始化结果容器
代码
void _Per(set<string>& hash, vector<int>& st, string s, string& str)
{
if(s.size() == str.size())
{
hash.insert(s);
return ;
}
for(int i = 0; i < str.size(); ++i)
{
if(!st[i])
{
st[i] = 1;
s.push_back(str[i]);
_Per(hash, st, s, str);
s.pop_back();
st[i] = 0;
}
}
}
vector<string> Permutation(string str)
{
vector<string> res;
vector<int> st(str.size());
set<string> hash;
_Per(hash, st, "", str);
for(auto s : hash)
{
res.push_back(s);
}
return res;
}
复杂度
时间复杂度
- 在最坏情况下(所有字符都不同),生成所有排列需要 O(n!) 次递归,每次递归的操作为插入字符串到
set
(平均时间复杂度为 O(log(n!)))。- 因此,总时间复杂度为 O(n!⋅log(n!))。
- 然而,由于
set
内部管理的复杂性,实际操作中的开销更接近于 O(n!)。空间复杂度
- 递归栈空间:
- 在最坏情况下,递归的深度为n,每次递归调用需要为局部变量(如
s
和st
)分配栈空间,因此递归栈的空间复杂度为 O(n)。- 数据结构:
vector<int> st
大小为 n,即 O(n)。set<string> hash
中存储 n! 个字符串,每个字符串长度为 n,因此需要 O(n⋅n!) 的空间。vector<string> res
中存储 n! 个字符串,每个字符串长度为 n,同样需要 O(n⋅n!) 的空间。所以总体空间复杂度为: O(n⋅n!)
总结
- 时间复杂度: O(n!)
- 空间复杂度: O(n⋅n!)
方法二:库函数
思路
使用库函数:prev_permutation
和next_permutation
他们的功能分别是:寻找前一个字典序排序,寻找下一个字典序排序。
代码
vector<string> Permutation(string str)
{
string s1 = str;
string s2 = str;
vector<string> res;
res.push_back(s1);
while(prev_permutation(s1.begin(), s1.end()))
{
res.push_back(s1);
}
while(next_permutation(s2.begin(), s2.end()))
{
res.push_back(s2);
}
return res;
}
复杂度
时间复杂度:
每次库函数
prev_permutation
和next_permutation
的调用都是O(n)的时间复杂度。总共是n!次调用,所以整体的时间复杂度是O(n * n!);
空间复杂度:
两个临时字符串是O(n), 而返回的答案数组是O(n * n!)的空间复杂度
所以空间复杂度:O(n * n!);
方法三:第一种方法的优化->回溯
思路
优化:
- 优化了空间,没有使用set去重,而是在排序的基础上,进行去重
为什么这样就可以跳过重复的字符
if (i > 0 && str[i] == str[i - 1] && !used[i - 1]) continue;
代码
public:
vector<string> Permutation(string str) {
vector<string> result;
if (str.empty()) return result;
sort(str.begin(), str.end()); // 排序字符串
vector<bool> used(str.length(), false);
string current;
backtracking(str, result, current, used);
return result;
}
private:
void backtracking(const string &str, vector<string> &result, string ¤t, vector<bool> &used) {
// 基础条件:如果当前长度等于原始字符串长度,我们找到了一个排列
if (current.length() == str.length()) {
result.push_back(current);
return;
}
for (int i = 0; i < str.length(); ++i) {
// 跳过相同字符
if (i > 0 && str[i] == str[i - 1] && !used[i - 1]) continue;
if (!used[i]) {
used[i] = true;
current.push_back(str[i]);
backtracking(str, result, current, used);
current.pop_back();
used[i] = false;
}
}
}
复杂度
时间复杂度
因为采用了回溯的思路:每次新生成一个排列的时候时间复杂度接近O(1);
所以整体的时间复杂度为:O(n!);
空间复杂度
答案数组O(n * n!).其中递归函数的栈帧为O(n * n!);