题目描述:输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例: 输入:s = "abc" 输出:["abc","acb","bac","bca","cab","cba"]
1、使用C++ next_permutation()全排列函数
class Solution {
public:
vector permutation(string s)
{
sort(s.begin(),s.end());
vector ans;
//next_permutation是一个全排列函数,可以输出下一个值更大的排列
//next_permutation()会取得[first,last)所标示之序列的下一个排列组合,
//如果没有下一个排列组合,便返回false; 否则返回true。
do
{
ans.push_back(s);
}while(next_permutation(s.begin(),s.end()));
return ans;
}
};
2、 递归回溯
一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
去重最为关键的代码为:
if (i > 0 && s[i] == s[i - 1] && visited[i - 1] == false) { continue; }
如果改成 used[i - 1] == true
, 也是正确的!,去重代码如下:
if (i > 0 && s[i] == s[i - 1] && visited[i - 1] == true) { continue; }
如果要对树层中前一位去重,就用visited[i - 1] == false,如果要对树枝前一位去重用visited[i - 1] == true。
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
class Solution {
public:
vector res;
string path;
vector permutation(string s)
{
res.clear();
path.clear();
sort(s.begin(),s.end()); //排序用来去重
vector visited(s.size(),false);
backtrack(s,visited);
return res;
}
void backtrack(string s,vector& visited)
{
//递归终止条件:字符串s的所有字符都遍历了一遍
if(s.size() == path.size())
{
res.push_back(path); //找到一条符合题意的路径
return;
}
for(int i=0;i0 && s[i] == s[i-1] && visited[i-1] == false)
if(i>0 && s[i] == s[i-1] && visited[i-1] == true) //字符串存在字符相同的情况
{
continue;
}
if(visited[i] == false) //当前字符没有遍历
{
path.push_back(s[i]); //将未遍历的字符插入 path 字符数组中
visited[i] = true; //标注该字符已访问
backtrack(s,visited); //递归访问下一个未访问的字符
path.pop_back(); //回溯,删除最后一位字符
visited[i] = false;
}
}
}
};
class Solution {
public:
vector res; //返回结果
vector visited; //标记数组,用来记录元素是否被访问过
vector permutation(string s)
{
sort(s.begin(),s.end());
string temp = "";
visited.resize(s.size(),false);
backtrack(s,temp);
return res;
}
void backtrack(string s,string& temp)
{
//递归终止条件
if(temp.size() == s.size())
{
res.push_back(temp);
return;
}
for(int i = 0; i < s.size(); i++)
{
//避免在同一位置填入重复元素
if(visited[i] || (i>0 && s[i] == s[i-1] && visited[i-1] == false))
{
continue;
}
temp.push_back(s[i]);
visited[i] = true;
backtrack(s,temp);
temp.pop_back();
visited[i] = false;
}
}
};
总结:子集、组合类问题,关键是用一个start参数来控制选择列表!!若有重复元素,应该先排序后去重!最后回溯六步走:
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
总结:可以发现“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数