并查集知识
在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:功能:其有两个基本操作:
1.合并两个不相交的集合。(把顶点元素合并为同一个)
2.判断两个元素是否属于同一个集合 (查看顶点元素)
1:Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
2:Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。
情侣牵手
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)定义了两个用于此数据结构的操作:
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
- Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。
具体分析:参考链接
- 我们设想一下加入有两对情侣互相坐错了位置,我们至多只需要换一次。
如果三对情侣相互坐错了位置,A1+B2,B1+C2,C1+A2。他们三个之间形成了一个环,我们只需要交换两次。 - 如果四队情侣相互坐错了位置,即这四对情侣不与其他情侣坐在一起,A1+B2,B1+C2,C1+D2,D1+A2.他们四个之间形成了一个环,他们只需要交换三次并且不用和其他情侣交换,就可以将这四对情侣交换好,以此类推,其实就是假设k对情侣形成一个环状的错误链,我们只需要交换k - 1次就可以将这k对情侣的位置排好。
- 所以问题转化成N对情侣中,有几个这样的错误环。
- 我们可以使用并查集来处理,每次遍历相邻的两个位置,如果他们本来就是情侣,他们处于大小为1的错误环中,只需要交换0次。如果不是情侣,说明他们呢两对处在同一个错误环中,我们将他们合并(union),将所有的错坐情侣合并和后,答案就是情侣对 - 环个数。
- 这也说明,最差的情况就是所有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;
}
};