输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
1 <= s 的长度 <= 8
这本身是一个全排列的题,但是并没有要求按照顺序来,使用回溯法即可。这道题难点在于有重复的元素,所以需要去重。
回溯法:
先用sort给数组排序,使相同元素相邻,使用一个visited数组来记录每个节点的状态,使用一个vector来存放节点,节点未遍历且相邻节点不是重复字符时,将节点加入到vector中,依次递归。
class Solution {
public:
vector<string> permutation(string s) {
vector<string> res;
if(s.empty()) return res;
sort(s.begin(), s.end());
int len = s.size();
vector<bool> visited(len, false);
string temp;
backtrack(res, s, temp, visited);
return res;
}
void backtrack(vector<string> &res, string s, string &temp, vector<bool> &visited) {
if(temp.size() == s.size()) {
res.push_back(temp);
return;
}
for(int i = 0; i < s.size(); i++) {
if(visited[i]) continue;
/*
比如有两个相邻元素xx,visited[i-1] = 1, visited[i] = 0表明第一个x加进去temp中了,所以不用continue;
如果visited[i-1] = 0, 表明已经完成两个x的操作,此时只剩下一个x了,无法构成排列,直接continue
*/
if(i > 0 && !visited[i-1] && s[i] == s[i-1]) continue; //剪枝
visited[i] = true;
temp.push_back(s[i]);
backtrack(res, s, temp, visited);
temp.pop_back();
visited[i] = false;
}
}
};
这里需要注意和理解的就是剪枝那一步。
假如有两个相同元素xx相邻,这里我们用 x1 和 x2 代替这两个 x 。重复的情况一定就是本来是xx,我们产生 x1x2 和 x2x1 两种情况。为了避免这种情况,我们就需要剪枝,我们便利是从前往后的,那么我们会先遇到 x1,然后令 visited[x1] = 1,当遍历到 x2 的时候,此时 x1 是遍历过的,也就是生成了x1x2这种结果。那么我们就需要在后面避免 x2x1 这种情况,这种情况的出现条件就是 visited[x1] = 0,然后遇到 x2 ,此时就可以直接continue。
交换法
每次固定一个字符,然后将固定字符与其他字符进行交换,依次遍历每个字符,再进行回溯递归。
使用set进行去重
class Solution {
public:
vector<string> permutation(string s) {
set<string> res;
backtrack(s, 0, res);
return vector<string>(res.begin(), res.end());
}
void backtrack(string s, int start, set<string> &res) {
// 结束条件
if(start == s.size() - 1) {
res.insert(s);
return;
}
for(int i = start; i < s.size(); i++) {
swap(s[i], s[start]);
backtrack(s, start+1, res);
swap(s[i], s[start]);
}
}
};
与方案二方法一致,但不是使用set去重,而实定义一个函数进行去重
class Solution {
public:
vector<string> permutation(string s) {
vector<string> res;
backtrack(s, 0, res);
return res;
}
void backtrack(string &s, int start, vector<string> &res){
if(start == s.size() - 1){
res.push_back(s);
return;
}
for(int i = start; i < s.size(); i++){
if(judge(s, start, i)) continue; //从start开始,遍历不重复的字符
swap(s[start], s[i]);
backtrack(s, start+1, res);
swap(s[start], s[i]);
}
}
bool judge(string& s, int start, int end) {
for (int i = start; i < end; ++i) {
if (s[i] == s[end]) return true;
}
return false;
}
};
方案二并没有忽略掉相同元素之间的交换,所以存在重复解,需要靠set去重,而方案三在每一次遍历的时候都只判断不重复的字符,也就是只交换不相同的元素,这样就不会存在重复解。
judge函数里start到end-1里均是不同的元素,每当往后添加一个元素时,如何判断是否是重复元素,就是让start到end-1分别与end做判断,如果重复就跳过。
这样将set换成vector,同时剪枝就会使程序时间降低。