题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
DFS思路
核心要点
- dfs(str, pos):
在已经固定了前pos个字符的情况下,dfs对后面的字符串进行排列组合,即[pos,str.length()] - 怎么排列组合:
通过确定首位字符(即下标为pos的字符),然后继续对后面的字符串进行排列组合 - 怎么确定首位str[pos]:
[pos,str.length()]中的每个字符按顺序站一次首位,注意判断是否和之前站过首位的重复了
- 时间复杂度:O(n!),n是指字符串长度,时间复杂度和字符串排列的方案数成线性关系n*(n-1)*…*1
- 空间复杂度:O(n),全排列的递归深度为 N ,系统累计使用栈空间大小为 O(N)
去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换
对于字符串的排列问题:
如果能生成n-1个元素的全排列,就能生成n个元素的全排列。对于只有一个元素的集合,可以直接生成全排列。所以全排列的递归终止条件很明确,只有一个元素时。我们可以分析一下全排列的过程:
- 首先,我们固定第一个字符a,求后面两个字符bc的排列
- 当两个字符bc排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列
- 现在是把c放在第一个位置的时候了,但是记住前面我们已经把原先的第一个字符a和后面的b做了交换,为了保证这次c仍是和原先处在第一个位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在交换b和a之后,再拿c和处于第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列
- 既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了
class Solution {
public:
//去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换
vector<string> Permutation(string str) {
if(str=="")
return anws;
//在已经固定了前0个字符的情况下,dfs对后面的字符串进行排列组合,即[0,str.length()]
dfs(str, 0);
//保证按字典序打印
sort( anws.begin(), anws.end() );
return anws;
}
/*
1、dfs(str, pos):
在已经固定了前pos个字符的情况下,dfs对后面[pos,str.length())的字符串进行排列组合
2、怎么排列组合:
通过确定首位字符(即下标为pos的字符),然后继续对后面的字符串进行排列组合
3、怎么确定首位str[pos]:
[pos,str.length())中的每个字符按顺序站一次首位,注意判断是否和之前站过首位的重复了
*/
void dfs(string str, int pos){
if( pos == str.length() ){ //已固定的位置和字符串长度相等,说明一个新的排列已经完成,保存
anws.push_back(str);
return;
}
//确定首位字符str[pos]:[pos,str.length())中的每个字符按顺序站一次首位
for(int i=pos; i<str.length(); i++){
//判断当前准备站首位的字符str[i],是否和之前站过首位str[pos,i)的重复了
if( isRepeated(str,pos,i) )
continue;
//当前这个非重复的字符str[i]交换到pos的位置
swap(str[pos], str[i]);
// 继续对后面的字符串进行排列组合
dfs(str, pos+1);
//完成一次站首位并排列组合后,记得回退操作,恢复str的字符顺序,准备下一个字符的站首位
swap(str[pos], str[i]);
}
}
private:
vector<string> anws;
//判断字符串str[start,pos)中是否存在str[pos]的重复字符
bool isRepeated(string str, int start, int pos ){
for( int i = start; i < pos; i++ ){
if( str[i] == str[pos] )
return true;
}
return false;
}
};