字符串的排列:最直观的想法是,回溯中的全排列,只不过该全排列中的元素可能重复,故需要考虑去重。使用res表示所有结果,使用path表示当前结果,使用used表示元素是否遍历,使用backtracking表示回溯。如果path的长度等于str的长度,那么表明已经到达叶子节点,故需要将path加入到res中并返回;直接依次遍历str中的每一个元素,使用used判断当前元素是否使用过,如果使用过则继续下一轮continue,反之标记当前元素已经被使用并将其加入到当前路径中,同时继续回溯,回溯完后再弹出元素并置为未访问。由于全排列中的元素可能重复,但是结果数组要求不重复,故使用unordered_set去重,最后使用迭代器将其转换为vector<string>即可。注意,回溯类型的题目,无论是排列还是组合,都可以通过画树形图来模拟解决。以字符串"ABC"为例,先A再B再C,到C时回溯返回弹出C,然后backtracking结束回到B再弹出B,当时i=1,故继续i=2,此时C加入,然后再回溯到B,就完成ABC和ACB,其余的以此类推。
unordered_set<string> res; //结果数组 string path; //当前结果 void backtracking(vector<bool> &used,string &str) { //当前结果数组等于字符串长度则收集结果 if(path.size()==str.size()) { res.emplace(path); return; } //遍历字符串元素 for(int i=0;i<str.size();i++) { //如果已经使用过则下一轮 if(used[i]==true) continue; //标记使用 used[i]=true; //加入当前路径 path.push_back(str[i]); //继续回溯 backtracking(used, str); //回溯结束后置为未访问以留下一次 used[i]=false; //弹出元素 path.pop_back(); } } vector<string> Permutation(string str) { if(str.size()==0) //空字符串 return vector<string>({""}); //使用数组 vector<bool> used(str.size(),false); backtracking(used, str); return vector<string>(res.begin(),res.end()); }
优化:上述方法中,使用了额外的存储空间,那么能否不使用呢?当然可以!我们可以发现,上述的字符串重复,主要是由于相同元素引起的,那么我们可以通过先对字符串进行排序,然后再结合used数组和str字符串来进行去重,即树枝上的元素可以重复,但是树层上的元素不可以重复,因为如果树层上重复,那么就出现了同前面相同的排列。
vector<string> res; //结果数组 string path; //当前结果 void backtracking(vector<bool> &used,string &str) { //结果数组等于字符串长度则收集结果 if(path.size()==str.size()) { res.push_back(path); return; } //遍历字符串元素 for(int i=0;i<str.size();i++) { //树枝上可以重复树层上不可以重复 used[i-1]=false表示树层上重复 因为是回溯后的结果 if(str[i]==str[i-1]&&i>=1&&used[i-1]==false) continue; //如果已经使用过则下一轮 if(used[i]==true) continue; //标记使用 used[i]=true; //加入当前路径 path.push_back(str[i]); //继续回溯 backtracking(used, str); //回溯结束后置为未访问以留下一次 used[i]=false; //弹出元素 path.pop_back(); } } vector<string> Permutation(string str) { if(str.size()==0) //空字符串 return vector<string>({""}); //使用数组 vector<bool> used(str.size(),false); sort(str.begin(),str.end()); //该方法一定要先排序 backtracking(used, str); return res; }
PS:牛客可能会给你报错,但是你自己对结果和输出即可!