LeetCode - 并查集类题目总结

并查集知识

在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:功能:其有两个基本操作:

1.合并两个不相交的集合。(把顶点元素合并为同一个)
2.判断两个元素是否属于同一个集合 (查看顶点元素)

1:Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
2:Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。

并查集参考链接1
并查集参考链接2
并查集参考链接3

情侣牵手

N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

示例 1: 输入: row = [0, 2, 1, 3], 输出: 1。解释: 我们只需要交换row[1]和row[2]的位置即可。
示例 2: 输入: row = [3, 2, 0, 1],输出: 0。解释: 无需交换座位,所有的情侣都已经可以手牵手了。

说明: len(row) 是偶数且数值在 [4, 60]范围内。可以保证row 是序列 0…len(row)-1 的一个全排列。

分析1: 贪心交换
(1)按排找情侣,第一排如果不是一对情侣,找第一个人的情侣在哪,将第二个人与目标情侣换位置;如果是一对情侣,则直接找第二排情侣;
(2)在找第二排情况时,按(1)步骤即可;直到所有排都已经情侣配对;
记录换座位的次数即可;

时间复杂度: O(N^2),其中 N 为情侣对的数量。空间复杂度: O(1),互相交换不需要开辟额外的空间。

class Solution {
public:
	int minSwapsCouples(vector<int>& row) {
		int rowSize = row.size();
		int dest;
		int cnt = 0;
		for (int i = 0; i < rowSize - 1; i = i + 2) {
			dest = (row[i] % 2 == 0) ? (row[i] + 1) : (row[i] - 1);
			if (dest == row[i + 1]) {
				continue;
			}
			for (int j = i + 2; j < rowSize; j++) {
				if (row[j] == dest) {
					row[j] = row[i + 1];
					row[i + 1] = dest;
					cnt++;
					break;
				}
			}
		}
		return cnt;

	}
};

分析2: 并查集。在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:

  1. Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  2. Union:将两个子集合并成同一个集合。

由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。

具体分析:参考链接

  1. 我们设想一下加入有两对情侣互相坐错了位置,我们至多只需要换一次。
    如果三对情侣相互坐错了位置,A1+B2,B1+C2,C1+A2。他们三个之间形成了一个环,我们只需要交换两次。
  2. 如果四队情侣相互坐错了位置,即这四对情侣不与其他情侣坐在一起,A1+B2,B1+C2,C1+D2,D1+A2.他们四个之间形成了一个环,他们只需要交换三次并且不用和其他情侣交换,就可以将这四对情侣交换好,以此类推,其实就是假设k对情侣形成一个环状的错误链,我们只需要交换k - 1次就可以将这k对情侣的位置排好。
  3. 所以问题转化成N对情侣中,有几个这样的错误环。
  4. 我们可以使用并查集来处理,每次遍历相邻的两个位置,如果他们本来就是情侣,他们处于大小为1的错误环中,只需要交换0次。如果不是情侣,说明他们呢两对处在同一个错误环中,我们将他们合并(union),将所有的错坐情侣合并和后,答案就是情侣对 - 环个数。
  5. 这也说明,最差的情况就是所有N对情侣都在一个环中,这时候我们需要N - 1调换。最好情况每对情侣已经坐好了,已经有N个大小为1的环,这时候我们需要N - N次调换。
/*  ====算法分析====
    并查集 
    1.首先初始状态把这N对情侣分别构成一个连通块
    2.考虑k对情侣相互错开的情况,他们形成一个环,可以知道需k-1次交换使排列正确
    3.这样相互错开的情况,分别构成连通块,最后用N - 连通块个数即为答案
    例如:0,1|2,3|... |2N-2,2N-1
    |0 3| ... |7 2|...|6 1| 看相对顺序,可以发现这三对构成一个环,只需2次交换
    同理还有其他类型的环构成连通块
*/

const int N = 80;
int p[N];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int n = row.size();

        // 初始化p[x]数组
        for (int i = 0; i < n; i += 2) {
            p[i + 1] = i;
            p[i] = i;   
        }

        // |0 3| ... |7 2|...|6 1| 合并0,3   7,2  6,1 最终这两个人到同一连通块
        for (int i = 0; i < n; i += 2) {
            p[find(row[i + 1])] = find(row[i]);  
        }

        int res = n / 2;
        for (int i = 0; i <= n; i ++) {
            if (p[i] == i) res --;
        }

        return res;
    }
};

最长连续序列

给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。

示例: 输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

分析1: 将能构成连续序列的数字合并在一类。本题就是个分类问题。用并查集记录H每个数字所属的类。例如:[100, 4, 200, 1, 3, 2]

第一步,初始每个类都属于本身的类H[100]=100,H[4]=4,H[200]=200,H[1]=1,H[3]=3,H[2]=2。

第二步,类之间建立关联。对每个数字x,看x-1是否在已知的类中。如果x-1是一个类,建立联系:H[x]=x-1。
H[100]=100,H[4]=3,H[200]=200,H[1]=1,H[3]=2,H[2]=1.

第三步,统计H中,每个类的数字个数。

并查集数据结构不复杂。其最关键的功能是查找。给出一个数字x,找出其分类。
在递归查找并加上高度压缩。本题用哈希表存储H更方便。

class Solution {
public:
	int longestConsecutive(vector<int>& nums) {
		unordered_map<int, int> H;
		for (auto x : nums) {
			H[x] = x;
		}
		for (auto x : nums) {
			if (H.count(x - 1)) {
				H[x] = x - 1;
			}
		//find(H, x); 可以去掉
		}

		unordered_map<int, int> C;
		for (auto p : H) {
			int r = find(H, p.first);
			++C[r];
		}
		int ans = 0;
		for (auto x : C) {
			ans = max(ans, x.second);
		}
		return ans;
	}

	int find(unordered_map<int, int>& H, int x) {
		if (H[x] != x)
			H[x] = find(H, H[x]);
		return H[x];
	}
};

分析2: 哈希表查找,每个连续序列的最小值才开始执行while循环,计算连续序列的数目。

class Solution {
public:
     int longestConsecutive(vector<int>& nums) {
        unordered_set<int> H(nums.begin(),nums.end());
        int ans = 0;
        for(auto x : nums){
            if(H.count(x-1)) continue;
            int a=x;
            int b=1;
            while(H.count(a+1)){
                a+=1;
                b+=1;
            }
            ans = max(ans,b);
        }
        return ans;
    }
};
发布了76 篇原创文章 · 获赞 6 · 访问量 2769

猜你喜欢

转载自blog.csdn.net/u014618114/article/details/104288038