一月的力扣每日一题似乎是并查集月,在一月的最后一天,总结一下并查集的解题模板吧。在解决图论相关问题的时候,如果我们不关心图的路径如何连接,而只关心图的连通性和连通分量的个数,我们可以考虑采用并查集来解决。
并查集最重要的两个操作是查找和合并操作。同时,为了提高并查集的合并和查找效率,在查找的时候我们可以执行路径压缩,在合并的时候我们可以采取按秩合并的方案。
接下来我总结了并查集这个数据结构的标准模板,包括了合并与查找操作。
class UnionFind {
public:
vector<int> parent; //存储父节点
vector<int> size; //存储当前节点所在连通分量的节点合数,便于按秩合并操作
int n;
// 当前连通分量数目
int setCount;
public: //构造函数,执行初始化操作
UnionFind(int _n): n(_n), setCount(_n), parent(_n), size(_n, 1) {
iota(parent.begin(), parent.end(), 0);
}
int findset(int x) { //寻找根节点,并执行了路径压缩算法
return parent[x] == x ? x : parent[x] = findset(parent[x]);
}
bool unite(int x, int y) { //连接两个节点所在连通分量
x = findset(x); //x的父节点
y = findset(y); //y的父节点
if (x == y) { //在同一个连通分量中
return false;
}
if (size[x] < size[y]) { //按秩合并的判断,x所在连通分量元素小于y
swap(x, y);
}
parent[y] = x; //把y所在联通分量的根节点设置为x
size[x] += size[y]; //同时x所在联通分量的规模变成两者之和
--setCount; //联通分量个数-1
return true;
}
bool connected(int x, int y) { //判断是否在同一个连通分量之中
x = findset(x);
y = findset(y);
return x == y;
}
};
以上就是并查集的标准模板,根据模板,我们来解决1.31号的每日一题,题目是hard级别,但是其实只要熟悉并查集,基本是秒杀。
题干如下:
读完题目,我们可以发现,如果把每个字符串当做一个节点,把两个字符串是否相似当做两个节点是否有边,这就是一个图论问题。其实题目要求的就是图的连通分量个数,可以采用并查集解答。
解答如下:
class Solution {
public: //解决方案:并查集
vector<int> root;
//寻找当前连通分量的根节点,并且采用路径压缩优化连通分量
int findRoot(int x){
return root[x] == x ? x : root[x] = findRoot(root[x]);
}
//判断两个字符串是否相似
bool check(string& s1, string& s2, int len){
int num = 0; //统计不同字符的个数
for(int i = 0;i<len;i++){
if(s1[i] != s2[i]){
num++;
}
if(num > 2){
return false;
}
}
return true;
}
int numSimilarGroups(vector<string>& strs) {
int n = strs.size(); //字符串数量
int len = strs[0].size(); //一个字符串长度
root.resize(n); //给向量分配n个元素空间
for(int i = 0;i<n;i++){
root[i] = i;
}
for(int i = 0;i<n;i++){
for(int j = i+1;j<n;j++){
int r1 = findRoot(i); //寻找根节点
int r2 = findRoot(j);
if(r1 == r2){ //已经在同一个连通分量里,直接跳过
continue;
}
if(check(strs[i],strs[j],len)){ //判断是否相似,相似则合并
root[r1] = r2; //r1,r2交换位置无所谓
}
}
}
int res = 0;
for(int i = 0;i<n;i++){ //统计连通分量的个数
if(root[i] == i){
res++;
}
}
return res;
}
};