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

Posted nefu-ljw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣 每日一题 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 的系数)。

以上是关于力扣 每日一题 886. 可能的二分法难度:中等,rating: 1794(并查集 / 拆点优化的扩展域并查集)的主要内容,如果未能解决你的问题,请参考以下文章

力扣 每日一题 870. 优势洗牌难度:中等(贪心+双指针)

每日一题886. 可能的二分法

力扣 每日一题 870. 优势洗牌难度:中等,rating: 1648(贪心+双指针)

力扣 每日一题 934. 最短的桥难度:中等,rating: 1825(dfs / bfs)

力扣 每日一题 856. 括号的分数难度:中等(栈 / 思维计数&括号深度)

力扣 每日一题 811. 子域名访问计数难度:中等,rating: 1377(字符串切分+哈希表计数)