文章目录
前言
通过并查集(一),相信读者已经对并查集有了很深刻的理解。下面,本篇文章会通过几道LeetCode上面的题目,带领大家巩固并查集的相关知识点。
一、多余的边
1.1 题目链接
1.2 题目描述
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
1.3 题目代码
class Solution {
int Find(vector<int>& pre, int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Join(vector<int>& pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
vector<int> pre(1010);
for(int i = 1; i <= n; ++i){
pre[i] = i;
}
for(int i = 0; i < edges.size(); ++i){
int x = edges[i][0];
int y = edges[i][1];
if(Find(pre, x) != Find(pre, y)){
Join(pre, x, y);
}
else{
return {x, y};
}
}
return {};
}
};
1.4 解题思路
(1) 遍历边,使用并查集将边连接的两个点加入到同一个集合中。如果在加入之前,这两个点已经在这个集合中,说明产生了环,只需要返回这两个点即可。(题目已经保证了,只需要删除一条冗余边就能满足了。
二、可能的二分法
2.1 题目链接
2.2 题目描述
给定一组 n 人(编号为 1, 2, …, n), 我们想把每个人分进任意大小的两组。每个人都可能不喜欢其他人,那么他们不应该属于同一组。
给定整数 n 和数组 dislikes ,其中 dislikes[i] = [ai, bi] ,表示不允许将编号为 ai 和 bi的人归入同一组。当可以用这种方法将所有人分进两组时,返回 true;否则返回 false。
2.3 题目代码
class Solution {
int Find(vector<int>&pre , int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Union(vector<int>& pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
int state[n+5][n+5];
vector<int> pre(n+5);
memset(state, 0, sizeof(state));
for(int i = 1; i <= n; ++i){
pre[i] = i;
}
for(int i = 0; i < dislikes.size(); ++i){
int x = dislikes[i][0];
int y = dislikes[i][1];
state[x][y] = 1;
state[y][x] = 1;
}
for(int i = 1; i <= n; ++i){
int flag = 0;
int t = -1;
for(int j = 1; j <= n; ++j){
if(state[i][j] == 1){
if(flag == 0){
t = j;
flag = 1;
}
else{
if(Find(pre, t) != Find(pre, j)){
Union(pre, t, j);
}
}
}
}
}
for(int i = 0; i < dislikes.size(); ++i){
if(Find(pre, dislikes[i][0]) == Find(pre ,dislikes[i][1])){
return false;
}
}
return true;
}
};
2.4 解题思路
(1) 用邻接矩阵的方式来存储不喜欢的状态。
(2) 因为分成两组,所以每个人不喜欢的人都应该放在与这个人不同的另一个组,所以使用并查集来进行并操作。
(3) 最后遍历最初的不喜欢人的数组,如果不喜欢的两个人通过并查集在一个分组了就返回false,否则返回true。
三、连通网络的操作次数
3.1 题目链接
3.2 题目描述
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
3.3 题目代码
class Solution {
int Find(vector<int>& pre, int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Union(vector<int>&pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
public:
int makeConnected(int n, vector<vector<int>>& connections) {
vector<int> pre(n);
if(connections.size() < n - 1){
return -1;
}
for(int i = 0; i < n; ++i){
pre[i] = i;
}
for(int i = 0; i < connections.size(); ++i){
int x = connections[i][0];
int y = connections[i][1];
if(Find(pre, x) != Find(pre, y)){
Union(pre, x, y);
}
}
int hash[n+5];
memset(hash, 0, sizeof(hash));
for(int i = 0; i < n; ++i){
int num = Find(pre, i);
hash[num] = 1;
}
int cnt = 0;
for(int i = 0; i < n; ++i){
if(hash[i] == 1){
++cnt;
}
}
return cnt-1;
}
};
3.4 解题思路
(1) 如果总的连接的线的根数,连n - 1都没有,那么一定不能将n台电脑连接起来。
(2) 使用并查集,将所有已经相连的电脑放置在同一个集合中。
(3) 记录集合的个数,最终最少操作次数等于集合数 减去 1。
四、等式方程的可满足性
4.1 题目链接
4.2 题目描述
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。
4.3 题目代码
class Solution {
int Find(vector<int>& pre, int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Union(vector<int>& pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
public:
bool equationsPossible(vector<string>& equations) {
vector<int> pre(26);
for(int i = 0; i < 26; ++i){
pre[i] = i;
}
for(int i = 0; i < equations.size(); ++i){
int x = equations[i][0] - 'a';
int y = equations[i][3] - 'a';
char z = equations[i][1];
if(z == '='){
if(Find(pre, x) != Find(pre, y)){
Union(pre, x, y);
}
}
}
for(int i = 0; i < equations.size(); ++i){
int x = equations[i][0] - 'a';
int y = equations[i][3] - 'a';
char z = equations[i][1];
if(z == '!'){
if(Find(pre, x) == Find(pre, y)){
return false;
}
}
}
return true;
}
};
4.4 解题思路
(1) 利用并查集,将相等的元素放在一个集合当中。
(2) 然后再次遍历,如果两个元素不相等却在一个集合当中那么就判断为false。
(3) 如果没有判出false,就为true。
五、寻找图中是否存在路径
5.1 题目链接
5.2 题目描述
有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。
给你数组 edges 和整数 n、source 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。
5.3 题目代码
class Solution {
int Find(vector<int>& pre, int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Union(vector<int>& pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
public:
bool equationsPossible(vector<string>& equations) {
vector<int> pre(26);
for(int i = 0; i < 26; ++i){
pre[i] = i;
}
for(int i = 0; i < equations.size(); ++i){
int x = equations[i][0] - 'a';
int y = equations[i][3] - 'a';
char z = equations[i][1];
if(z == '='){
if(Find(pre, x) != Find(pre, y)){
Union(pre, x, y);
}
}
}
for(int i = 0; i < equations.size(); ++i){
int x = equations[i][0] - 'a';
int y = equations[i][3] - 'a';
char z = equations[i][1];
if(z == '!'){
if(Find(pre, x) == Find(pre, y)){
return false;
}
}
}
return true;
}
};
5.4 解题思路
(1) 运用并查集,将相连的点并入一个集合当中。
(2) 通过并查集的查找,判断source和destination是否在同一个集合当中。
六、统计完全连通分量的数量
6.1 题目链接
6.2 题目描述
给你一个整数 n 。现有一个包含 n 个顶点的 无向 图,顶点按从 0 到 n - 1 编号。给你一个二维整数数组 edges 其中 edges[i] = [ai, bi] 表示顶点 ai 和 bi 之间存在一条 无向 边。
返回图中 完全连通分量 的数量。
如果在子图中任意两个顶点之间都存在路径,并且子图中没有任何一个顶点与子图外部的顶点共享边,则称其为 连通分量 。
如果连通分量中每对节点之间都存在一条边,则称其为 完全连通分量 。
6.3 解题代码
class Solution {
int Find(vector<int>& pre, int index){
while(pre[index] != index){
index = pre[index];
}
return index;
}
void Join(vector<int>& pre, int index1, int index2){
index1 = Find(pre, index1);
index2 = Find(pre, index2);
if(index1 != index2){
pre[index1] = index2;
}
}
unordered_map<int, int> hash;
unordered_map<int, int> point;
public:
int countCompleteComponents(int n, vector<vector<int>>& edges) {
int m = edges.size();
vector<int> pre(100);
for(int i = 0; i < n; ++i){
pre[i] = i;
}
for(int i = 0; i < m; ++i){
int x = edges[i][0];
int y = edges[i][1];
Join(pre, x, y);
}
for(int i = 0; i < m; ++i){
int x = edges[i][0];
hash[Find(pre, x)]++;
}
for(int i = 0; i < n; ++i){
point[Find(pre, i)]++;
}
int res = 0;
for(int i = 0; i < n; ++i){
if(i == pre[i]){
if(hash[i] == (point[i] * (point[i] - 1)) / 2){
res++;
}
}
}
return res;
}
};
6.4 解题思路
(1) 这道题目是问有多少个完全通量,那么我们首先要知道有多少个子图。那么我们可以用并查集来统计有多少个子图。
(2) 我们将并查集中统领子图中的所有节点的节点所连接的边和所拥有的点的数量统计出来。
(3) 遍历所有子图,已知点的数量n,已知边的数量m,如果m 等于(n * n-1) / 2,那么完全通量+1即可。
总结
本篇文章中的题目难度适中,建议读者按照需求将其完成。