题目来源
题目描述
class Solution {
public:
int numSimilarGroups(vector<string>& strs) {
}
};
题目解析
题目大意:给出一个字符串数组,要求找出这个数组中,“不相似”的字符串有多少种?
“相似字符串”的定义:
- 如果A和B字符串只需要交换一次字母的位置就能变成两个相等的字符串,那么A和B是相似的
- 即:A和B相似,有两种情况:
- A == B
- A和B只有两个字符不相等,其他字符都相等,这样交换一次才能完全相等
- 而且题目中说了,字符串数组是“字母异位词”,即字符的种类和个数都完全一样,只是顺序不同
那么,什么叫做“相似字符串组呢”?即相似字符串组中的每个字符串都有另外至少一个字符串和它相似。比如对于 {“tars”, “rats”, “arts”} 这个相似字符串组而言,相似关系是 “tars” <=> “rats” <=> “arts” 。
可以看到“相似字符串组之间的关系有传递性,对于这种群组分类问题,是并查集的经典应用场合:
- 我们可以将每个字符串看成是一个节点
- 如果两个字符串相似,就将它们merge
- 最后数有多少个联通分量即可。
怎么判断字符串A和字符串B是否相似呢?
,只要按位置对比字符,若不相等则 diff 自增1,若 diff 大于2了直接返回 false,因为只有 diff 正好等于2或者0的时候才相似。
这个题目作为hard 有点名不副实,理解清楚题意即可
class Solution {
class UnionFind{
private:
std::vector<int> parent_; // parent[i] = k : i的父亲是k
std::vector<int> size_; // size[i] = k : 如果i是代表节点,size[i]才有意义( i所在的集合大小是多少),否则无意义
std::vector<int> help_; // 辅助结构
int cnt_; //一共有多少个集合
int findRoot(int i){
int hi = 0;
while (i != parent_[i]){
help_[hi++] = parent_[i];
i = parent_[i];
}
for (hi--; hi >= 0; --hi) {
parent_[help_[hi]] = i;
}
return i;
}
public:
explicit UnionFind(int n){
cnt_ = n;
parent_.resize(n);
size_.resize(n);
help_.resize(n);
for (int i = 0; i < n; ++i) {
parent_[i] = i;
size_[i] = 1;
}
}
void merge(int i, int j){
int f1 = findRoot(i);
int f2 = findRoot(j);
if(f1 != f2){
if(size_[f1] >= size_[f2]){
parent_[f2] = f1;
size_[f1] = size_[f1] + size_[f2];
}else{
parent_[f1] = f2;
size_[f2] = size_[f2] + size_[f1];
}
--cnt_;
}
}
int counts() const{
return cnt_;
}
bool isConnected(int i, int j){
return findRoot(i) == findRoot(j);
}
};
public:
int numSimilarGroups(vector<string>& strs) {
int N = strs.size();
UnionFind unionFind(N);
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
// 已经连接了,不用判断相似性
if(unionFind.isConnected(i, j)){
continue;
}
if(isSimilar(strs[i], strs[j])){
unionFind.merge(i, j);
}
}
}
return unionFind.counts();
}
private:
bool isSimilar(const string& A,const string& B){
int cnt = 0;
for(int i = 0; i < A.size(); i++){
cnt += (A[i] != B[i]);
}
return cnt <= 2;
}
};