Leetcode进阶之路——并查集

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013700358/article/details/86612431

关于并查集的基础理解,网上已有很多,推荐这篇博客并查集
讲得很细,个人认为对理解很有帮助,而我这里则单纯举个例子帮助理解:
一个班有26个人,分别用A~Z标记
假设:如果A和B是朋友,B和C是朋友,那么A和C也是朋友(间接的),于是A、B、C三个人组成了一个小圈子,这时候如果D和B也是朋友,那么ABCD四个人就是一个小圈子
那么怎么判断这若干人是否同属于一个圈子呢?
先设置一个数组pre[N],其中pre[i] = j表示i的老大是j
可以假定A为这个圈子的老大(其实对于这种无向的,假定谁都可以)
那么根据A和B是朋友,就可以令pre[B] = A,而pre[A] = A, 说明B的老大就是A
这时,再根据B和C,第一步pre[C] = B, pre[B] = A, 那么C的老大也是A
用代码来表述就是:

	int find(int x)
	{
		int t = x;
		while (pre[t] != t)
			t = pre[t];
		return t;
	}

	void mix(int a, int b)
	{
		int fa = find(a), fb = find(b);
		if (fa != fb)
			pre[fa] = fb;
	}

上面两段是并查集的核心代码
第一段是用来找x的“根”, 如上面的例子中,A的根是本身,B的根原本是B,转换后变为A,C的根原本也是本身,转换后为A
第二段则是实现上述“转换”的步骤
最开始提到的两道例题,其实LeetCode中也有相似的题:
547. Friend Circles

There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a direct friend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.
Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise not. And you have to output the total number of friend circles among all the students.
Example 1:
Input:
[[1,1,0],
[1,1,0],
[0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.
Example 2:
Input:
[[1,1,0],
[1,1,1],
[0,1,1]]
Output: 1
Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends,
so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1.

同样的例子,只不过输出的是圈子的数目
Success代码如下:

class Solution {
public:
    int findCircleNum(vector<vector<int>>& M) {
        memset(father, 0, sizeof(father));
        for(int i = 0; i < M.size(); ++i)
            father[i] = i;
        int cnt = 0;
        for(int i = 0; i < M.size(); ++i)
        {
            for(int j = i + 1; j < M[i].size(); ++j)
                if(M[i][j] == 1) 
					merge(i, j);
        }
        for(int i = 0; i < M.size(); ++i)
                if(father[i] == i) cnt++;
        return cnt;
    }
    
    int find(int x)
    {
        while(father[x] != x)
        {
            x = father[x];
        }
        return x;
    }
    
    void merge(int a, int b)
    {
        int x = find(a), y = find(b);
        if(x != y)
            father[x] = y;
    }
    
private:
    int father[10000];
};

根据题意可以知道,给出的矩阵一定是一个对角线对称的矩阵,因此不需要遍历N*N次,而是遍历一半即可,如果发现M[i][j] = 1, 则father[i][j] = 1
最后统计所有father[i] = i 的数目,即圈子数(若father[i] = j 则说明i属于j所在的圈子)

第二题:684. Redundant Connection

In this problem, a tree is an undirected graph that is connected and has no cycles.
The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v.
Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v.
Example 1:
Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given undirected graph will be like this:
1
/
2 - 3
Example 2:
Input: [[1,2], [2,3], [3,4], [1,4], [1,5]]
Output: [1,4]
Explanation: The given undirected graph will be like this:
5 - 1 - 2
| |
4 - 3

题意:给定的二维数组中,每个元素包含两个数字i, j, 表示i 和 j连通,找到一条边,使得删除这条边后,不改变原图的连通性,若包含多种选择,则删除最后出现的那条边
先给出Success代码:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        if(edges.size()  < 2) return {};
		memset(father, 0, sizeof(father));
		for (int i = 0; i <= edges.size(); ++i)
			father[i] = i;
		vector<int> res;
		for (int i = 0; i < edges.size(); ++i)
		{
			if (find(edges[i][0]) != find(edges[i][1]))
				merge(edges[i][0], edges[i][1]);
			else
			{
				res = edges[i];
				break;
			}
		}
		return res;
	}
    
    int find(int x)
	{
		int t = x;
		while (father[t] != t)
			t = father[t];
		return t;
	}

	void merge(int a, int b)
	{
		int fa = find(a), fb = find(b);
		if (fa != fb)
			father[fa] = fb;
	}
    
private:
    int father[10000];
};

核心代码与上一题一样,只不过只采用了一次循环,当发现已有两个顶点都能指向同一个根节点时,则该边多余,可删除。

总体而言如果看出来是并查集,先把find函数和merge函数写了,思路也就理了一大半
第二题还有一个变形,685. Redundant Connection II,上面两个例题都是无向边,而这个题是有向边,除了核心的两部分代码,还需要考虑当存在环时,会存在一个节点有可能对应两个根节点的情况,这时候就要判断这两条边是

  1. 都在环内,则随便删除哪一条都可以
  2. 一条在环外, 一条环内,则删除环内的边

若有阐述不当或不完整的地方还望指出,谢谢!

扫描二维码关注公众号,回复: 5568191 查看本文章

猜你喜欢

转载自blog.csdn.net/u013700358/article/details/86612431