力扣 每日一题 886. 可能的二分法【难度:中等,rating: 1794】(并查集 / 拆点优化的扩展域并查集)

题目链接

https://leetcode.cn/problems/possible-bipartition/

题目来源于:第97场周赛 Q3 rating: 1794

思路一(建图+并查集)

关键:总共只能分成两组,那么对于每个点,其所有不喜欢的点必须放在同一组。

建无向图,连所有边(a dislike b,则a和b连接一条双向边)。然后遍历所有点 u,与 u 相连的所有点 v 合并到同一个集合里面,合并之后检查 u 和 v 是否也变成了同一集合,如果是就发生了冲突,不满足条件。

代码一

class Solution {
    static const int N=2010;
    int fa[N];
	
	// 找x的祖先,并且把经过的点的father值也设置为x的祖先
    int find(int x){
        if(x!=fa[x]){
            fa[x]=find(fa[x]);
        }
        return fa[x];
    }

    // a合并到b
    void join(int a,int b){
        fa[find(a)]=find(b);
    }

public:
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        for(int i=1;i<=n;i++){
            fa[i]=i;
        }

        vector<int> edges[n+1]; // n+1个vector
        for(int i=0;i<dislikes.size();i++){
            int x=dislikes[i][0];
            int y=dislikes[i][1];
            edges[x].push_back(y);
            edges[y].push_back(x);
        }

        for(int u=1;u<=n;u++){
            if(edges[u].empty()){ // 可能有的点没有连边
                continue;
            }
            // u的敌人集合{v},将vi(i>=1)的组类别设置为v0
            int v0=edges[u][0];
            for(int i=1;i<edges[u].size();i++){
                int vi=edges[u][i];
                join(vi,v0);
                if(find(u)==find(vi)){
                    // 在将vi加入v0组之后,u和vi也变成了同一组,产生了冲突
                    return 0;
                }
            }
        }
        return 1;
    }
};

思路二(扩展域并查集)

在题解区学到的一种方法,比官方题解的并查集做法更加优化。

优化:扩展域并查集,进行拆点,无需建图,节省了空间和时间。

关键:每个点拆成两个点,例如 点-a 拆出一个 点-反a,编号为 a+n。和 反a 在同一集合的意义在于表示不能和 a 同一集合。这样就能直接判断两个互相不喜欢的点是否在同一集合中。

扩展域并查集可以维护敌人或者更为复杂的关系。一般的并查集都是维护朋友,即两个点在一个分组里。这道题目给的条件是敌人,即[a,b], [a,c] 意味着ab是敌人,ac是敌人,bc必须在一个组里面。所以每个点都抽象出一个反节点(所以总数是2n)。这样就可以通过反节点求出所有的朋友,比如[a,b], [a,c] 那么a+n同时和b和c在一组,也就是敌人的敌人都是朋友。 –引用评论

代码二

// 对于每个人,他的所有不喜欢的人必须放在一组
// 推广:a dislike b <=> a -> b(反) 且 b -> a(反)
class Solution {
    static const int N=2010*2;
    int fa[N];
    
    // 找x的祖先,并且把经过的点的father值也设置为x的祖先
    int find(int x){
        if(x!=fa[x]){
            fa[x]=find(fa[x]);
        }
        return fa[x];
    }

    // a合并到b
    void join(int a,int b){
        fa[find(a)]=find(b);
    }

public:
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        for(int i=1;i<=n*2;i++){
            fa[i]=i;
        }
        for(int i=0;i<dislikes.size();i++){
            int x=dislikes[i][0];
            int y=dislikes[i][1];
            join(x,y+n); // x和反y合并,表示x不能和y合并
            join(y,x+n); // y和反x合并,表示y不能和x合并
            if(find(x)==find(y)){ // 在之前合并操作之后,是否满足x,y不在同一个组
                return 0;
            }
        }
        return 1;
    }
};

/*
10
[[4,7],[4,8],[5,6],[1,6],[3,7],[2,5],[5,8],[1,2],[4,9],[6,10],[8,10],[3,6],[2,10],[9,10],[3,9],[2,3],[1,9],[4,6],[5,7],[3,8],[1,8],[1,7],[2,4]]
ans: true

10
[[1,2],[3,4],[5,6],[6,7],[8,9],[7,8]]
ans: true
*/

其他解法

本题实质是判断二分图,也可以用dfs“染色”方法来判断。(本文不作介绍)

补充资料:并查集的时间复杂度

参考:https://leetcode.cn/problems/number-of-provinces/solutions/550060/jie-zhe-ge-wen-ti-ke-pu-yi-xia-bing-cha-0unne/

本文的并查集使用的是路径压缩算法,时间复杂度 O ( α ( n ) ) O(α(n)) O(α(n))

这里 α 表示阿克曼函数的反函数,在宇宙可观测的 n 内(例如宇宙中包含的粒子总数),α(n) 不会超过 5(一般算法竞赛中 α(n) 认为是一个小于等于 5 的系数)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nefu-ljw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值