1. 0990. 等式方程的可满足性
1.1 题目大意
描述:给定一个由字符串方程组成的数组 equations
,每个字符串方程 equations[i]
的长度为 4
,有以下两种形式组成:a==b
或 a!=b
。a
和 b
是小写字母,表示单字母变量名。
要求:判断所有的字符串方程是否能同时满足,如果能同时满足,返回 True
,否则返回 False
。
class UnionFind:
def __init__(self):
# 初始化一个字典来存储每个元素的父节点
self.parent = {}
def find(self, x):
# 如果元素不在字典中,将其添加进去,父节点设置为自身
if x not in self.parent:
self.parent[x] = x
# 如果元素的父节点不是自身,递归地找到根节点
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
# 返回元素的根节点
return self.parent[x]
def union(self, x, y):
# 找到x和y的根节点
rootX = self.find(x)
rootY = self.find(y)
# 如果根节点不同,将x的根节点的父节点设置为y的根节点
if rootX != rootY:
self.parent[rootX] = rootY
def connected(self, x, y):
# 检查x和y是否连接,即它们是否有相同的根节点
return self.find(x) == self.find(y)
class Solution(object):
def equationsPossible(self, equations):
# 创建一个UnionFind实例
uf = UnionFind()
# 第一遍:将所有相等的元素合并到同一个集合中
for eq in equations:
if eq[1] == '=':
uf.union(eq[0], eq[3])
# 第二遍:检查是否存在矛盾的不等式
for eq in equations:
if eq[1] == '!':
# 如果不等式中的两个元素在同一个集合中,返回False
if uf.connected(eq[0], eq[3]):
return False
# 如果没有矛盾,返回True
return True
2. 1202. 交换字符串中的元素
2.1 题目大意
描述:给定一个字符串 s
,再给定一个数组 pairs
,其中 pairs[i] = [a, b]
表示字符串的第 a
个字符可以跟第 b
个字符交换。只要满足 pairs
中的交换关系,可以任意多次交换字符串中的字符。
要求:返回 s
经过若干次交换之后,可以变成的字典序最小的字符串。
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点指向自己,秩为0
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
# 查找节点x的根节点,并进行路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并两个节点所在的集合
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
rootX, rootY = rootY, rootX
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
def connected(self, x, y):
# 判断两个节点是否在同一集合中
return self.find(x) == self.find(y)
class Solution(object):
def smallestStringWithSwaps(self, s, pairs):
n = len(s)
# 创建并查集实例
uf = UnionFind(n)
# 合并可以交换的索引对
for x, y in pairs:
uf.union(x, y)
# 创建一个字典,用于存储每个连通分量中的字符
components = {}
for i in range(n):
root = uf.find(i)
if root not in components:
components[root] = []
components[root].append(s[i])
# 对每个连通分量中的字符进行排序
for comp in components.values():
comp.sort(reverse=True)
# 构建最终的字符串
result = []
for i in range(n):
root = uf.find(i)
result.append(components[root].pop())
return ''.join(result)
3. 0947. 移除最多的同行或同列石头
3.1 题目大意
描述:二维平面中有 n
块石头,每块石头都在整数坐标点上,且每个坐标点上最多只能有一块石头。如果一块石头的同行或者同列上有其他石头存在,那么就可以移除这块石头。
给你一个长度为 n
的数组 stones
,其中 stones[i] = [xi, yi]
表示第 i
块石头的位置。
要求:返回可以移除的石子的最大数量。
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点指向自己,秩为0
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
# 查找节点x的根节点,并进行路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并两个节点所在的集合
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
rootX, rootY = rootY, rootX
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
def connected(self, x, y):
# 判断两个节点是否在同一集合中
return self.find(x) == self.find(y)
class Solution(object):
def removeStones(self, stones):
n = len(stones)
# 创建并查集实例
uf = UnionFind(n)
# 遍历所有石头,如果两块石头在同一行或同一列,则将它们合并到同一个集合中
for i in range(n):
for j in range(i):
if stones[i][0] == stones[j][0] or stones[i][1] == stones[j][1]:
uf.union(i, j)
# 计算连通分量的数量,即无法相互到达的石头集合的数量
return n - len({uf.find(i) for i in range(n)})
1. 0547. 省份数量
1.1 题目大意
描述:有 n
个城市,其中一些彼此相连,另一些没有相连。如果城市 a
与城市 b
直接相连,且城市 b
与城市 c
直接相连,那么城市 a
与城市 c
间接相连。
「省份」是由一组直接或间接链接的城市组成,组内不含有其他没有相连的城市。
现在给定一个 n * n
的矩阵 isConnected
表示城市的链接关系。其中 isConnected[i][j] = 1
表示第 i
个城市和第 j
个城市直接相连,isConnected[i][j] = 0
表示第 i
个城市和第 j
个城市没有相连。
要求:根据给定的城市关系,返回「省份」的数量。
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点指向自己,秩为0
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
# 查找节点x的根节点,并进行路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并两个节点所在的集合
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
rootX, rootY = rootY, rootX
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
def connected(self, x, y):
# 判断两个节点是否在同一集合中
return self.find(x) == self.find(y)
class Solution(object):
def findCircleNum(self, isConnected):
n = len(isConnected)
# 创建并查集实例
uf = UnionFind(n)
# 遍历所有城市,如果两个城市相连,则将它们合并到同一个集合中
for i in range(n):
for j in range(i):
if isConnected[i][j] == 1:
uf.union(i, j)
# 计算连通分量的数量,即朋友圈的数量
return len({uf.find(i) for i in range(n)})
2. 0684. 冗余连接
2.1 题目大意
描述:一个 n
个节点的树(节点值为 1~n
)添加一条边后就形成了图,添加的这条边不属于树中已经存在的边。图的信息记录存储与长度为 n
的二维数组 edges
,edges[i] = [ai, bi]
表示图中在 ai
和 bi
之间存在一条边。
现在给定代表边信息的二维数组 edges
。
要求:找到一条可以山区的边,使得删除后的剩余部分是一个有着 n
个节点的树。如果有多个答案,则返回数组 edges
中最后出现的边。
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点指向自己,秩为0
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
# 查找节点x的根节点,并进行路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并两个节点所在的集合
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
rootX, rootY = rootY, rootX
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
def connected(self, x, y):
# 判断两个节点是否在同一集合中
return self.find(x) == self.find(y)
class Solution(object):
def findRedundantConnection(self, edges):
n = len(edges)
# 创建并查集实例
uf = UnionFind(n + 1)
# 遍历所有边,如果边的两个顶点已经相连,则这条边是多余的
for edge in edges:
if uf.connected(edge[0], edge[1]):
return edge
else:
uf.union(edge[0], edge[1])
# 如果没有多余的边,返回空列表
return []
3. 0765. 情侣牵手
3.1 题目大意
描述:$n$ 对情侣坐在连续排列的 2×𝑛 个座位上,想要牵对方的手。人和座位用 0∼2×𝑛−1 的整数表示。情侣按顺序编号,第一对是 (0,1),第二对是 (2,3),以此类推,最后一对是 (2×𝑛−2,2×𝑛−1)。
给定代表情侣初始座位的数组 row
,row[i]
表示第 i
个座位上的人的编号。
要求:计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。每一次交换可以选择任意两人,让他们互换座位。
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点指向自己,秩为0
self.parent = list(range(n))
self.rank = [0] * n
def find(self, x):
# 查找节点x的根节点,并进行路径压缩
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
# 合并两个节点所在的集合
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
rootX, rootY = rootY, rootX
self.parent[rootY] = rootX
if self.rank[rootX] == self.rank[rootY]:
self.rank[rootX] += 1
def connected(self, x, y):
# 判断两个节点是否在同一集合中
return self.find(x) == self.find(y)
class Solution(object):
def minSwapsCouples(self, row):
n = len(row)
# 创建并查集实例
uf = UnionFind(n // 2)
# 遍历所有情侣,如果两个人在同一行且相邻,则将它们合并到同一个集合中
for i in range(0, n, 2):
x = row[i] // 2
y = row[i + 1] // 2
uf.union(x, y)
# 计算连通分量的数量,即无法相互交换的情侣集合的数量
return n // 2 - len({uf.find(i) for i in range(n // 2)})
并查集是一种高效的数据结构,用于处理一些不相交集合的合并及查询问题。它主要支持两种操作:合并(Union)和查找(Find)。
### 并查集的实现思路
1. **快速查询(基于数组实现)**
- 使用数组表示集合元素,数组索引作为元素的集合编号。
- 初始化时,每个元素的集合编号唯一。
- 合并操作需要更新一个集合中所有元素的编号。
- 查找操作通过比较两个元素的编号是否相同来判断它们是否属于同一集合。
2. **快速合并(基于森林实现)**
- 使用森林(多棵树)表示多个集合,每棵树的根节点是集合的代表元素。
- 子节点指向父节点,与常规树结构不同。
- 合并操作通过连接两棵树的根节点实现。
- 查找操作通过递归访问元素的父节点直到根节点。
### 优化策略
1. **路径压缩**
- 在查找操作中,将非根节点直接连接到根节点,减少树的深度。
- 有两种压缩方式:隔代压缩和完全压缩。
2. **按秩合并**
- 在合并操作中,根据树的深度或大小来决定连接方向,避免树的不平衡。
- 有两种合并方式:按深度合并和按大小合并。
### 并查集的算法分析
- **空间复杂度**:O(n),使用数组存储元素。
- **时间复杂度**:在应用路径压缩和按秩合并优化后,操作时间复杂度接近O(1),最坏情况下为O(m * α(n)),其中m是操作次数,α(n)是Ackermann函数的反函数,增长极慢。