PROBLEM
剑指 Offer 38. 字符串的排列
难度 中等
MY ANSWER
使用回溯,vis标记访问过的字符,使用set记录得到的结果,若重复则不保存到最终结果中。时间空间复杂度都很高,因为没有剪枝。
class Solution { public: vector<string> res; string s; vector<int> vis; set<string> ans; void select(string str) { if(str.length() == s.length()) { if(ans.insert(str).second) { res.push_back(str); } return; } else { for(int i = 0; i < vis.size(); i++) { if(!vis[i]) { vis[i] = 1; select(str + s[i]); vis[i] = 0; } } } } vector<string> permutation(string s) { this->s = s; vis.resize(s.length()); select(""); return res; } };
|
BETTER SOLUTION
回溯剪枝
首先将字符串排序,当有重复的字符,只选择第一个进行回溯,就避免了重复的字符串。时间复杂度O(n*n!),空间复杂度O(n)。
class Solution { public: vector<string> res; string s; vector<int> vis; void select(string str) { if(str.length() == s.length()) { res.push_back(str); return; } else { for(int i = 0; i < vis.size(); i++) { if(vis[i] || (i > 0 && !vis[i-1] && s[i-1] == s[i])) { continue; } vis[i] = 1; select(str + s[i]); vis[i] = 0; } } } vector<string> permutation(string s) { vis.resize(s.length()); sort(s.begin(), s.end()); this->s = s; select(""); return res; } };
|
下一个排列
首先将字符串排序,然后通过交换来生成下一个字典序的排列。具体做法就是从尾部开始朝前遍历,记录第一个降序的字符,将其与前面遍历过的第一个比它大的字符交换,再将后面的字符串反转,就得到了下一个字典序的字符串。时间复杂度O(n*n!),空间复杂度O(1)。
例子:1238640 -> 1248630 -> 1240368。
class Solution { public: bool nextPermutation(string& s) { int i = s.size() - 2; while (i >= 0 && s[i] >= s[i + 1]) { i--; } if (i < 0) { return false; } int j = s.size() - 1; while (j >= 0 && s[i] >= s[j]) { j--; } swap(s[i], s[j]); reverse(s.begin() + i + 1, s.end()); return true; }
vector<string> permutation(string s) { vector<string> ret; sort(s.begin(), s.end()); do { ret.push_back(s); } while (nextPermutation(s)); return ret; } };
|
SUMMARY
回溯算法模板backtrack
result = [] def backtrack(路径, 选择列表): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择
|
sort(begin迭代器, end迭代器),可排列字符串,直接在字符串上进行从小到大排列,即改变了原来的字符串。
全排列去重可以先对其进行排序,对重复选择的分支进行剪枝就可以去重。
reverse(begin迭代器, end迭代器),迭代器直接通过加法来访问后面的迭代器。