如果我们交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。
例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置); "rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars","rats",或 "arts" 相似。
总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}。注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。
我们给出了一个不包含重复的字符串列表 A。列表中的每个字符串都是 A 中其它所有字符串的一个字母异位词。请问 A 中有多少个相似字符串组?
示例:
输入:["tars","rats","arts","star"]
输出:2
提示:
A.length <= 2000
A[i].length <= 1000
A.length * A[i].length <= 20000
A 中的所有单词都只包含小写字母。
A 中的所有单词都具有相同的长度,且是彼此的字母异位词。
此问题的判断限制时间已经延长。
备注:
字母异位词[anagram],一种把某个字符串的字母的位置(顺序)加以改换所形成的新词。
58.88% 50.00%
方法1. 不用图
1、相似的string在一个set内, 这样L会慢慢减少,减少的L进行下一轮。
2、用list因为其删除/增加的效率高。
3、 L.erase(j++)必须这样写,不然会出错。因为修改list会影响iterator。
class Solution {
private:
bool _isValid(const string &x, const string &y) {
int len = x.size();
char diff = 0;
for (int i = 0; i < len; ++i) {
if (x[i] != y[i])
diff++;
if (diff > 2)
return false;
}
return true;
}
int _oneSet(list<string> &L, const int &step) {
int size = L.size();
list<string> tmp;
tmp.push_back(L.front());
L.pop_front();
for (auto i = tmp.begin(); i != tmp.end(); ++i) {
for (auto j = L.begin(); j != L.end();) {
if (_isValid(*i, *j)) {
tmp.push_back(*j);
L.erase(j++);
} else {
j++;
}
}
}
if (L.empty()) {
return step;
} else {
return _oneSet(L, step + 1);
}
}
public:
int numSimilarGroups(vector<string>& A) {
list<string> L(A.begin(), A.end());
return _oneSet(L, 1);
}
};
方法2. union-set 并查集 O(N^2 W) 建图
class Solution {
public:
int numSimilarGroups(vector<string>& A) {
int res = 0, n = A.size();
vector<int> root(n);
for (int i = 0; i < n; ++i) root[i] = i;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (!isSimilar(A[i], A[j])) continue;
root[getRoot(root, j)] = i;
}
}
for (int i = 0; i < n; ++i) {
if (root[i] == i) ++res;
}
return res;
}
int getRoot(vector<int>& root, int i) {
return (root[i] == i) ? i : getRoot(root, root[i]);
}
bool isSimilar(string& str1, string& str2) {
for (int i = 0, cnt = 0; i < str1.size(); ++i) {
if (str1[i] == str2[i]) continue;
if (++cnt > 2) return false;
}
return true;
}
};
方法3. 先构建图 O(N^2 W),然后深度遍历图去计算有几个连通子树
class Solution {
public:
bool isSim(const string& s1, const string& s2) {
if (s1.size() != s2.size()) return false;
int diff = 0;
for (int i = 0; i < s1.size(); ++i) {
if (s1[i] != s2[i]) {
++diff;
if (diff > 2) return false;
}
}
return true;
}
void dfs(vector<vector<int> >& g, int i, vector<bool>& seen) {
seen[i] = true;
for (auto j : g[i]) {
if (!seen[j]) {
dfs(g, j, seen);
}
}
}
int numSimilarGroups(vector<string>& A) {
int N = A.size();
vector<vector<int > > g(N);
for (int i = 0; i < N; ++i) {
g[i].push_back(i);
for (int j = i + 1; j < N; ++j) {
if (isSim(A[i], A[j])) {
g[i].push_back(j);
g[j].push_back(i);
}
}
}
vector<bool> seen(N, false);
int res = 0;
for (int i = 0; i < N; ++i) {
if (!seen[i]) {
++res;
dfs(g, i, seen);
}
}
return res;
}
};