力扣 每日一题 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“染色”方法来判断。(本文不作介绍)
补充资料:并查集的时间复杂度
本文的并查集使用的是路径压缩算法,时间复杂度 O ( α ( n ) ) O(α(n)) O(α(n))。
这里 α 表示阿克曼函数的反函数,在宇宙可观测的 n 内(例如宇宙中包含的粒子总数),α(n) 不会超过 5(一般算法竞赛中 α(n) 认为是一个小于等于 5 的系数)。
以上是关于力扣 每日一题 886. 可能的二分法难度:中等,rating: 1794(并查集 / 拆点优化的扩展域并查集)的主要内容,如果未能解决你的问题,请参考以下文章
力扣 每日一题 870. 优势洗牌难度:中等(贪心+双指针)
力扣 每日一题 870. 优势洗牌难度:中等,rating: 1648(贪心+双指针)
力扣 每日一题 934. 最短的桥难度:中等,rating: 1825(dfs / bfs)