算法专练:并查集

一,下面的题目并查集只需要套用模板即可,模板如下:

	#define  maxn 256//根据题目数据量自定义maxn
    int fa[maxn];
    void union_init(){
        for(int i=0;i<maxn;++i){
            fa[i]=i;
        }
    }//首先对每个结点的父结点初始化为本身
    int  union_find(int x){
        return fa[x]==x?x:(fa[x]=union_find(fa[x]));
    }//查找到x所在集合的代表元素,这里同时做了路径压缩处理
    bool union_union(int x,int y){
        int fx=union_find(x);
        int fy=union_find(y);
        if(fx==fy){
            return false;
        }
        fa[fx]=fa[fy];
        return true;
    }//将x,y集合到一起,并判断是否之前已经集合过,判断这一步可加可不加根据题目需要来

二,题目:

1.990. 等式方程的可满足性

原题链接


        给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a== b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

        只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。

        示例

        输入:[“a= =b”,“b!=a”]

        输出:false

        示例

        输入:[“b= =a”,“a==b”]

        输出:true

        提示:

        1 <= equations.length <= 500

        equations[i].length == 4

        equations[i][0] 和 equations[i][3] 是小写字母

        equations[i][1] 要么是 ‘=’,要么是 ‘!’

        equations[i][2] 是 ‘=’


        这道题就很简单了,我们根据每个equations中的元素是否是相等关系,如果是相等关系的话就把他们放到一个集合中去,当所有相等元素都在一起了我们去看所有的不相等关系,如果这些元素我们之前已经把他们集合在一起了,这就说明前后矛盾,返回false,当所有的关系都检查过没有错误就返回true.

class Solution {
    #define  maxn 256//其实用不了这么多,因为只有小写字母是我们需要处理的元素,这里纯属懒
    int fa[maxn];
    void union_init(){
        for(int i=0;i<maxn;++i){
            fa[i]=i;
        }
    }
    int  union_find(int x){
        return fa[x]==x?x:(fa[x]=union_find(fa[x]));
    }
    bool union_union(int x,int y){
        int fx=union_find(x);
        int fy=union_find(y);
        if(fx==fy){
            return false;
        }
        fa[fx]=fa[fy];
        return true;
    }
public:
    bool equationsPossible(vector<string>& equations) {
        int i=0;
        union_init();//并查集使用之前一定要初始化
        for(i=0;i<equations.size();++i){
            if(equations[i][1]=='='){
                union_union(equations[i][0],equations[i][3]);
            }
        }//先集合相等关系
        for(i=0;i<equations.size();++i){
            if(equations[i][1]=='!'){
                if(union_find(equations[i][0])==union_find(equations[i][3])){
                    return false;
                }//发现是不相等关系并且之前已经把他们当作相等关系集合在一起了,这个时候前后矛盾
            }
        }
        return true;
    }
};

2.1319. 连通网络的操作次数

原题链接


        用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。

        网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。

        给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。

        示例 1:

        输入:n = 4, connections = [[0,1],[0,2],[1,2]]

        输出:1

        示例 2:

        输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]]

        输出:2

        示例 3:

        输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2]]

        输出:-1

        解释:线缆数量不足。

        示例 4:

        输入:n = 5, connections = [[0,1],[0,2],[3,4],[2,3]]

        输出:0

        提示:

        1 <= n <= 10^5

        1 <= connections.length <= min(n*(n-1)/2, 10^5)

        connections[i].length == 2

        0 <= connections[i][0], connections[i][1] < n

        connections[i][0] != connections[i][1]

        没有重复的连接。

        两台计算机不会通过多条线缆连接。


        在这道题中我们可以通过并查集将给出的这些点放到不同的集合中去,并且在这个过程中统计边数。那么这个时候我们就有了k个连通分量(直白点就是并查集中的集合元素,连通分量为1说明原图已经是条通路了),edges条边。由于我们想做的是将这些连通分量都集合在一起,同时题目又说对已有的边可以任意的删除增加,那么这道题接下来就十分简单了。

        一个有n个顶点的图构成一条通路最少需要n-1条,而我们又有了k个连通分量,对于任意两个连通分量我们总可以利用一条边将他们集合在一起。考虑极端情况每个连通分量都是点,这样我们至少需要K-1条边,所以在边数少于K-1的时候属于非法情况我们直接返回-1,这是合法的前提。

        一般情况,我们知道对于一个连通分来说他本身至少是一个无环通路,也就是说假如一个连通分量有n个顶点,他其中至少有n-1条边。但是现在我们的连通分量数目不为1,并且边的数目也大于了n-1,也就是说这些连通分量中肯定有k-1条边使得k-1个连通分量构成了有环通路(无环通路只能有一个,这里自己思考一下就理解了,很形象的),那么在这个情况下我们把多出来的k-1条边拿出来是不会改变这些连通分量本身的连通性的,但是却可以把他们集合在一起。所以这个时候我们返回的数量就是k-1。

class Solution {
    #define  maxn 100010
    int fa[maxn];
    void union_init(){
        for(int i=0;i<maxn;++i){
            fa[i]=i;
        }
    }
    int  union_find(int x){
        return fa[x]==x?x:(fa[x]=union_find(fa[x]));
    }
    bool union_union(int x,int y){
        int fx=union_find(x);
        int fy=union_find(y);
        if(fx==fy){
            return false;
        }
        fa[fx]=fa[fy];
        return true;
    }//仍然是并查集模板
public:
    int makeConnected(int n, vector<vector<int>>& connections) {
        int i=0,edge=0;
        int hash[maxn];
        union_init();
        for(i=0;i<connections.size();++i){
            int a=connections[i][0];
            int b=connections[i][1];
            if(!union_union(a,b)){
                edge++;
            }
        }//构造连通分量并统计边数
        memset(hash,0,sizeof(hash));
        int cnt=0;
        for(i=0;i<n;++i){
            if(!hash[union_find(i)]){
                hash[union_find(i)]=1;
                ++cnt;
            }
        }//统计连通分量数目
        if(edge<cnt-1){
             return -1;
        }//边不够直接返回
        return cnt-1;//返回多余的边数
    }
};

3.886. 可能的二分法

原题链接


        给定一组 n 人(编号为 1, 2, …, n), 我们想把每个人分进任意大小的两组。每个人都可能不喜欢其他人,那么他们不应该属于同一组。

        给定整数 n 和数组 dislikes ,其中 dislikes[i] = [ai, bi] ,表示不允许将编号为 ai 和 bi的人归入同一组。当可以用这种方法将所有人分进两组时,返回 true;否则返回 false。

        示例 1:

        输入:n = 4, dislikes = [[1,2],[1,3],[2,4]]

        输出:true

        解释:group1 [1,4], group2 [2,3]

        示例 2:

        输入:n = 3, dislikes = [[1,2],[1,3],[2,3]]

        输出:false

        示例 3:

        输入:n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]

        输出:false

        提示:

        1 <= n <= 2000

        0 <= dislikes.length <= 104

        dislikes[i].length == 2

        1 <= dislikes[i][j] <= n

        ai < bi

        dislikes 中每一组都 不同


        这道题的话,刚读完题可能无从下手,不过把他抽象为判断一个二部图就很容易理解了。这里二部图我们这样构造,对于一个点来说,如果这个图是二部图的话在这个图中就只有两个部分,一部分不含他不喜欢的人,另一部分全是他不喜欢的人。(我的解释不好,理解意思就行了)。

        那么我们先利用dislike建立边集,对于一个点的边集出边全是他不喜欢的人。我们的操作就是遍历所有点的边集的时候先去判断他出边指向的元素跟他本身是否之前集合过,如果是直接返回false,如果不是就把当前遍历的元素跟该点边集的首元素集合。

        如果最后遍历完毕仍然没发现不合法元素就返回true

class Solution {
    #define maxn 2005
    int  fa[maxn];
    vector<vector<int>> edges;//边集,当然可以直接开maxn个,这里只是为了省点内存
    void unino_init(){
        for(int i=1;i<maxn;++i){
            fa[i]=i;
        }
    }
    int unino_find(int x){
        return x==fa[x]?x:fa[x]=unino_find(fa[x]);
    }
    void union_union(int x,int y){
        int fx=fa[x];
        int fy=fa[y];
        if(fx==fy){
            return;
        }
        fa[fx]=fy;
    }
public:
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        edges.resize(n+1);//开n+1个元素是为了下标不越界
        unino_init();
        for(int i=0;i<dislikes.size();++i){
            int u=dislikes[i][0];
            int v=dislikes[i][1];
            edges[u].push_back(v);
            edges[v].push_back(u);
        }//建立双向边,因为两人互相不喜欢
        for(int i=1;i<=n;++i){
            for(int j=0;j<edges[i].size();++j){
                if(unino_find(i)==unino_find(edges[i][j])){
                    return false;
                }
                //对于某个点的边集元素来说,如果之前的操作中已经把他们集合过了
                //但是此时他们彼此不喜欢这属于非法情况
                union_union(edges[i][0],edges[i][j]);//敌人的敌人就是朋友,先把他们集合,其他的以后再说
            }
        }
        return true;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值