导语
leetcode刷题笔记记录,主要记录题目包括:
知识点
并查集
并查集(Disjoint Set Union,简称 DSU)是一种数据结构,用于管理一组不相交的(disjoint)集合。这个数据结构支持如下几种主要操作:
- Union(x, y) :将包含元素 x 和元素 y 的两个集合合并为一个新的集合。
- Find(x) :查找包含元素 x 的集合的“代表元素”。这通常用于检查两个元素是否在同一集合中。
并查集的主要应用是解决一些与连接性或者网络有关的问题,例如:
- 连通分量:在一个无向图中,使用并查集可以快速地检查任意两个节点是否连通,即是否存在从一个节点到另一个节点的路径。
- 最小生成树:Kruskal 算法用于寻找一个图的最小生成树,其中并查集用于维护图中的连通分量。
- 等价关系:在一些应用中,你需要知道一组对象中哪些对象是“等价的”(具有相同的性质或关系),并查集可以用于此类问题。
数据结构实现:
通常,一个简单的并查集数据结构可以用一个一维数组(或哈希表)来实现,其中 parent[i] 存储元素 i 的“父元素”。在最开始时,每个元素自己就是一个集合,所以 parent[i] = i。
当执行 Union(x, y) 操作时,一般选择其中一个集合的代表元素作为另一个集合的父元素,这样两个集合就合并为一个新的集合了。
在 Find(x) 操作中,一般从元素 x 出发,不断地找它的父元素,直到找到这个集合的代表元素(即父元素是自己的元素)。
为了优化查找和合并的速度,通常还会使用一些额外的技巧,如路径压缩(Path Compression)和按秩合并(Union by Rank)。
这样,高效实现的并查集的 Find 和 Union 操作的时间复杂度可以近乎视为常数时间 O(1)。
Python中的并查集实现
class UnionFind:
def __init__(self, n):
self.parent = [i for i in range(n)]
def find(self, u):
if self.parent[u] == u:
return u
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def union(self, u, v):
u = self.find(u)
v = self.find(v)
if u == v:
return
self.parent[u] = v
# 使用示例
uf = UnionFind(5)
uf.union(0, 1)
uf.union(1, 2)
print(uf.find(0)) # 输出 0
print(uf.find(1)) # 输出 0
print(uf.find(2)) # 输出 0
Leetcode 1971. 寻找图中是否存在路径
题目描述
有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。
请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。
给你数组 edges 和整数 n、source 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。
示例 1:
输入: n = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2
输出: true
解释: 存在由顶点 0 到顶点 2 的路径:
- 0 → 1 → 2
- 0 → 2
示例 2:
输入: n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5
输出: false
解释: 不存在由顶点 0 到顶点 5 的路径.
提示:
1 <= n <= 2 * 1050 <= edges.length <= 2 * 105edges[i].length == 20 <= ui, vi <= n - 1ui != vi0 <= source, destination <= n - 1- 不存在重复边
- 不存在指向顶点自身的边
解法
本题目是最基础的并查集实现,只需要将edges中的边构成一个并查集,然后判断起始和终止是否在一个集合中即可。
完整代码如下:
# 定义UnionFind类
class UnionFind:
def __init__(self, n):
# 初始化并查集,每个节点的父节点初始时是它自己
self.parent = [i for i in range(n)]
# 查找节点u的根节点
def find(self, u):
# 如果节点u是根节点,返回u
if self.parent[u] == u:
return u
# 否则,递归地找到u的根节点,并执行路径压缩
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
# 合并两个集合
def union(self, u, v):
# 查找u和v的根节点
u = self.find(u)
v = self.find(v)
# 如果u和v已经在同一个集合,无需合并,直接返回
if u == v:
return
# 合并:将u的根节点设置为v
self.parent[u] = v
# 定义Solution类
class Solution:
def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
# 初始化并查集,大小为n
uf = UnionFind(n)
# 遍历图中的每条边,进行合并操作
for u, v in edges:
uf.union(u, v)
# 检查source和destination是否在同一个集合
# 如果是,说明存在从source到destination的路径
return uf.find(source) == uf.find(destination)
Leetcode 684. 冗余连接
题目描述
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。
示例 1:
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
示例 2:
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
提示:
n == edges.length3 <= n <= 1000edges[i].length == 21 <= ai < bi <= edges.lengthai != biedges中无重复元素- 给定的图是连通的
解法
参考代码随想录的思路,由于题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。如果有多个答案,则返回二维数组中最后出现的边。
那么我们就可以从前向后遍历每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。
如图所示:
节点A 和节点 B 不在同一个集合,那么就可以将两个 节点连在一起。(如果题目中说:如果有多个答案,则返回二维数组中最前出现的边。 那我们就要 从后向前遍历每一条边了)如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。
如图所示:
已经判断 节点A 和 节点B 在在同一个集合(同一个根),如果将 节点A 和 节点B 连在一起就一定会出现环。
代码如下:
# 定义一个并查集类 UnionFind
class UnionFind:
def __init__(self, n):
# 初始化并查集的父节点数组,数组大小为n+1(因为节点值从1开始)
self.parent = [i for i in range(n+1)]
# find函数用于查找节点u的根节点
def find(self, u):
# 如果u的父节点就是自己,说明u是根节点,直接返回u
if self.parent[u] == u:
return u
# 如果不是,递归地查找u的根节点,并进行路径压缩
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
# union函数用于合并两个集合
def union(self, u, v):
# 分别找到u和v的根节点
u = self.find(u)
v = self.find(v)
# 如果u和v的根节点相同,说明它们已经在同一个集合中,不需要合并
if u == v:
return
# 否则,合并这两个集合。这里直接将u的根节点设置为v
self.parent[u] = v
# 定义解决方案的类
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
# 初始化一个并查集,集合的大小为边的数量(也就是节点的数量)
uf = UnionFind(len(edges))
# 遍历给定的所有边
for u, v in edges:
# 如果u和v不在同一个集合中,合并这两个集合
if uf.find(u) != uf.find(v):
uf.union(u, v)
# 如果u和v已经在同一个集合中,那么添加这条边会形成一个环
# 所以这条边就是多余的,应该被删除
else:
return [u, v]