-
题目:
对于一个可能有重复字符的字符串s,得到它的全排列;
全排列需要不重不漏; -
思路:
1.库函数next_permutation:
该库函数不管s有没有重复元素,都可以用;
class Solution {
public:
vector<string> permutation(string s) {
vector<string> res;
sort(s.begin(),s.end());
do{
res.push_back(s);
}while(next_permutation(s.begin(),s.end()));
return res;
}
};
2.树层剪枝:
用state代替常规的used,进一步优化空间复杂度;
递归函数dfs作用是:遍历s,尝试将每个元素放到path的该位置上,与之对应的state表示的是s的哪些元素用过了;
class Solution {
private:
vector<string> ans;
string path;
void dfs(string& s, int state) {//用int state代替vector<bool> used
if (path.size() == s.size()) {
ans.push_back(path);
return;
}
for (int i = 0; i < s.size(); ++i) {
// used[i - 1] == true,说明同一树支nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && s[i] == s[i - 1] && !(state >> (i - 1) & 1)) continue;//树层剪枝
if (!(state >> i & 1)) {
path.push_back(s[i]);
dfs(s, state + (1 << i));//state的更新放在递归参数上,就不用回溯了
path.pop_back();//这种push_back就必须回溯,因为确实会对后面造成影响;
}
}
}
public:
vector<string> permutation(string s) {
sort(s.begin(), s.end());
dfs(s, 0);
return ans;
}
};
3.(yxc无敌解法)回溯:
与方法2的差别在于递归函数dfs作用是:固定s的一个元素,去遍历path的每个空位,与之对应的是state表示的是path哪些位置已经填了数了
class Solution {
public:
vector<string> ans;
string path;
//当前函数要确定s[u]放的位置,从start处开始找,当前的排列状态为state(例如s有n个元素,则用n位二进制state的某位为0代表空位,1地表该位置已经放了数了)
void dfs(string& s, int u, int start, int state) {//从start处开始找s[u]该放的位置,目前的的排列状态是state
if (u == s.size()) {//说明s[0]~s[s.size() - 1]都已经确定好位置了,即已经得到了一种排列方式
ans.push_back(path);
return;
}
//对于元素不同情况下的全排列,循环都该从0开始,然后依次枚举visted数组中的空位;
//但本题的字符串s是可能有重复字符的,相同字符只要所占位置相同,就会导致path重复,例如s[0]==s[1]==1,则s[0]放在下标0处s[1]放在下标1处,跟二者换换位置得到的全排列是一样的;
//我们可以先sort,让相同字符都相邻,并规定:若s[u] == s[u-1],则放完s[u - 1]后在放s[u]时,只能选取s[u-1]后面的空位来防止重复,例如s[0]==s[1]==1,若s[0]放在0处,s[1]可选的位置从1开始,不能放在0处,这样就防止了s[1]在0处s[0]在1处这种情况;
if (!u || s[u] != s[u - 1]) start = 0;//当前元素s[u]不是重复元素, 就跟常规全排列 一样从0开始找要放的位置;若s[u] == s[u-1],则s[u]可选的位置只能从s[u-1]后面开始
for (int i = start; i < s.size(); ++i) {//寻找s[u]可以放的位置
if (!(state >> i & 1)) {//i位置是空位
path[i] = s[u];
dfs(s, u + 1, i + 1, state + (1 << i));
//这里之所以不用回溯,因为state + (1 << i)作为参数传入下一层递归,而未改变当前层的state,因此虽然i位置确定填了s[u],但++i后的state仍认为i位置是空位,因此后续在需要填时会直接覆盖s[u]
}
}
}
vector<string> permutation(string s) {
path.resize(s.size());//因为在递归函数中,用path[i]对其赋值,而不是push_back
sort(s.begin(), s.end());//让相同元素相邻
dfs(s, 0, 0, 0);
return ans;
}
};
- 总结:
① 方法2,3的区别:方法2固定的是path位置,去往这里填元素;方法3固定的是s的一个元素,去选择它放的位置;
② 用int state 替代 vector used很巧妙:state >> i & 1用于判断state的下标 i 处是否为1;state += (1 << i )表示 i 位置现在不再是空位;
③ 递归函数中使用到了path[ i ] = 。。。,因此需要提前path.resize(。。。)对其初始化;