题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
分析
这道题包括了重复字符的情况,会稍微复杂一点。先考虑无重复字符的情况。
如果会回溯算法的话,那么这道题会很简单。或者说深度优先遍历也可,因为其本质上也就是回溯算法。visit 数组表示第 i 个字符是否已经访问过。每次递归,对字符串中所有字符遍历一次。当访问至最后一个字符时,将整个 string 加入 vector<string> 中。举例来说,初始时,遍历 a,b,c,首先是 a,然后对 a 进行 递归,再遍历,a 已经访问过,b 没有,那么 a + b(字符合并),再递归,下一轮得到 c,递归到终止条件,回退;得到 a + c,再递归,得到 a + c + b。。。(过程真得很难说清楚,可参考深度优先遍历算法)。
对于重复字符的情况,我直接就暴力解法了,对已经生成的 vector<string> 排序, 然后删除重复元素,后来看了别人的博客,有更好的解法。如果有更好的办法烦请告诉我,谢谢。
void permutation(vector<string> &per, string str, string now, int* visit) {
if (now.size() == str.size()) {
per.push_back(now);
return;
}
for (int i = 0; i < str.size(); ++i) {
if (visit[i] == 0) {
visit[i] = 1;
permutation(per, str, now + str[i], visit);
visit[i] = 0;
}
}
}
vector<string> Permutation(string str) {
vector<string> per;
if (str == "") {
return per;
}
// 使用队列会更快
int* visit = new int[str.size()];
for(int i = 0; i < str.size(); ++i) {
visit[i] = 0;
}
//vector<int> visit;
permutation(per, str, "", visit);
sort(per.begin(), per.end());
for (int i = 1; i < per.size(); ++i) {
if (per[i - 1] == per[i]) {
per.erase(per.begin() + i - 1);
}
}
return per;
}
这道题还有一种解法:将所有元素两两进行对换,所有的可能情况组合在一起,即为全排列。这个做法我自己想不到,是看了别人的博客才看懂的,还看了蛮久的。。。果然还是太菜(后面会贴上别人的博客)。
解释一下:从最后一个元素开始遍历,对每一个当前遍历到的元素,按照从前往后的顺序交换其和其后的每一个元素(如果不明白,就自己举个例子)。下面的代码蛮精妙的,我是想不到,非常值得学习。
去除重复值,有更好的做法,请看我下面贴的博客,我就不改了。。。
代码:
void permutation(vector<string> &per, string str, int begin) {
if (begin == str.size() - 1) {
per.push_back(str);
}
for (int i = begin; i < str.size(); ++i) {
swap(str[begin], str[i]);
permutation(per, str, begin + 1);
swap(str[begin], str[i]);
}
}
vector<string> Permutation2(string str) {
vector<string> per;
if (str == "") {
return per;
}
permutation(per, str, 0);
sort(per.begin(), per.end());
// 去除重复值
for (int i = 1; i < per.size(); ++i) {
if (per[i - 1] == per[i]) {
per.erase(per.begin() + i - 1);
}
}
return per;
}
参考文献:
字符串的全排列和组合算法