2023/2/2更新:
补充一个二维矩阵并查集的leetcode题目
130. 被围绕的区域
难度中等912
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
,找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例 1:
'O'
'X'
'O'
'O'
'X'
示例 2:
输入:board = [["X"]]
输出:[["X"]]
当然,使用dfs也可以做!
class Solution:
def solve(self, board) -> None:
"""
Do not return anything, modify board in-place instead.
"""
m, n = len(board), len(board[0])
def need_fill(x, y):
# print("seen:", seen)
board[x][y] = '#'
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
x2, y2 = x + dx, y + dy
if 0 <= x2 < m and 0 <= y2 < n and (x2, y2) and board[x2][y2] == 'O':
need_fill(x2, y2)
for j in range(n):
if board[0][j] == 'O':
need_fill(0, j)
if board[m - 1][j] == 'O':
need_fill(m - 1, j)
for i in range(1, m - 1):
if board[i][0] == 'O':
need_fill(i, 0)
if board[i][n - 1] == 'O':
need_fill(i, n - 1)
for i in range(m):
for j in range(n):
if board[i][j] == 'O':
board[i][j] = 'X'
elif board[i][j] == '#':
board[i][j] = 'O'
return board
我们用并查集写下,杜绝了繁琐的判定seen过程,将临界边上的O和其连接的O统统归到dummy node上:
class UionFind:
def __init__(self, cnt):
self.parent = {i:i for i in range(cnt)}
def union(self, i, j):
self.parent[self.find(i)] = self.find(j)
def find(self, i):
path = []
node = i
while node != self.parent[node]:
path.append(node)
node = self.parent[node]
root = node
for node in path:
self.parent[node] = root
return root
def is_connect(self, i, j):
return self.find(i) == self.find(j)
class Solution:
def solve(self, board) -> None:
"""
Do not return anything, modify board in-place instead.
"""
m, n = len(board), len(board[0])
uf = UionFind(m*n + 1)
dummy = m*n
def node(i, j):
return i*n + j
for i in range(m):
for j in range(n):
if board[i][j] == 'O':
if i == 0 or i == m-1 or j == 0 or j == n-1:
uf.union(node(i,j), dummy)
for dx, dy in [(1, 0), (0, 1)]:
i2, j2 = i + dx, j + dy
if i2 < m and j2 < n and board[i2][j2] == 'O':
uf.union(node(i, j), node(i2, j2))
for i in range(m):
for j in range(n):
if board[i][j] == 'O' and not uf.is_connect(node(i, j), dummy):
board[i][j] = 'X'
return board
---------以上为更新
547. 朋友圈
难度中等204
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
- N 在[1,200]的范围内。
- 对于所有学生,有M[i][i] = 1。
- 如果有M[i][j] = 1,则有M[j][i] = 1。
class Solution(object):
def findCircleNum(self, M):
"""
:type M: List[List[int]]
:rtype: int
"""
N = len(M)
self.init(N)
for i in range(0, N):
for j in range(i + 1, N):
if M[i][j]:
self.connect(i, j)
return self.count()
def count(self):
cnt = 0
for i,n in enumerate(self.father):
if self.father[i] == i:
cnt += 1
return cnt
def init(self, N):
self.father = [0] * N
for i in range(N):
self.father[i] = i
def connect(self, a, b):
self.father[self.find(a)] = self.find(b)
def find(self, node):
path = []
while self.father[node] != node:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
684. 冗余连接
难度中等92
在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边
组成的二维数组。每一个边
的元素是一对[u, v]
,满足 u < v
,表示连接顶点u
和v
的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v]
应满足相同的格式 u < v
。
示例 1:
输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
1
/ \
2 - 3
示例 2:
输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
| |
4 - 3
注意:
- 输入的二维数组大小在 3 到 1000。
- 二维数组中的整数在1到N之间,其中N是输入数组的大小。
更新(2017-09-26):
我们已经重新检查了问题描述及测试用例,明确图是无向 图。对于有向图详见冗余连接II。对于造成任何不便,我们深感歉意。
class Solution(object):
def findRedundantConnection(self, edges):
"""
:type edges: List[List[int]]
:rtype: List[int]
"""
N = len(edges)
self.init(N)
ans = [None, None]
for i,j in edges:
is_success, same_nodes = self.connect(i, j)
if not is_success:
ans = same_nodes
return ans
def connect(self, a, b):
f1 = self.find(a)
f2 = self.find(b)
if f1 != f2:
self.father[f1] = f2
return True, [None,None]
else:
return False, [a,b]
def init(self, N):
self.father = {}
for i in range(1, N+1):
self.father[i] = i
def find(self, node):
path = []
while self.father[node] != node:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
591. 连接图 III
中文
English
给一个图中的 n
个节点, 记为 1
到 n
. 在开始的时候图中没有边.
你需要完成下面两个方法:
-
connect(a, b)
, 添加一条连接节点 a, b的边 -
query()
, 返回图中联通区域个数
样例
例1:
输入:
ConnectingGraph3(5)
query()
connect(1, 2)
query()
connect(2, 4)
query()
connect(1, 4)
query()
输出:[5,4,3,3]
例2:
输入:
ConnectingGraph3(6)
query()
query()
query()
query()
query()
输出:
[6,6,6,6,6]
class ConnectingGraph3:
"""
@param a: An integer
@param b: An integer
@return: nothing
"""
def __init__(self, n):
# initialize your data structure here.
self.father = [0]*(n+1)
self.n = n
for i in range(1, n+1):
self.father[i] = i
self.zone_cnt = n
def find(self, p):
paths = []
while p != self.father[p]:
paths.append(p)
p = self.father[p]
root = p
for node in paths:
self.father[node] = root
return root
def connect(self, a, b):
# write your code here
f1 = self.find(a)
f2 = self.find(b)
if f1 != f2:
self.zone_cnt -= 1
self.father[f1] = f2
"""
@return: An integer
"""
def query(self):
# write your code here
return self.zone_cnt
当然,这个题目使用BFS也是可以的!我在面试一个小伙的时候他就这么做过。
class ConnectingGraph3:
"""
@param a: An integer
@param b: An integer
@return: nothing
"""
def __init__(self, n):
# initialize your data structure here.
self.seen = set()
self.node_map = {}
self.n = n
def connect(self, a, b):
# write your code here
if a not in self.node_map:
self.node_map[a] = set()
self.node_map[a].add(b)
if b not in self.node_map:
self.node_map[b] = set()
self.node_map[b].add(a)
"""
@return: An integer
"""
def query(self):
# write your code here
ans = 0
self.seen = set()
for i in range(1, self.n+1):
if i not in self.seen:
self.bfs(i)
ans += 1
return ans
def bfs(self, root):
q = collections.deque([root])
self.seen.add(root)
while q:
node = q.popleft()
if node not in self.node_map:
continue
for neighbor in self.node_map[node]:
if neighbor not in self.seen:
self.seen.add(neighbor)
q.append(neighbor)
434. 岛屿的个数II
中文
English
给定 n, m, 分别代表一个二维矩阵的行数和列数, 并给定一个大小为 k 的二元数组A. 初始二维矩阵全0. 二元数组A内的k个元素代表k次操作, 设第i个元素为 (A[i].x, A[i].y)
, 表示把二维矩阵中下标为A[i].x行A[i].y列的元素由海洋变为岛屿. 问在每次操作之后, 二维矩阵中岛屿的数量. 你需要返回一个大小为k的数组.
样例
样例 1:
输入: n = 4, m = 5, A = [[1,1],[0,1],[3,3],[3,4]]
输出: [1,1,2,2]
解释:
0. 00000
00000
00000
00000
1. 00000
01000
00000
00000
2. 01000
01000
00000
00000
3. 01000
01000
00000
00010
4. 01000
01000
00000
00011
样例 2:
输入: n = 3, m = 3, A = [[0,0],[0,1],[2,2],[2,1]]
输出: [1,1,2,2]
注意事项
设定0表示海洋, 1代表岛屿, 并且上下左右相邻的1为同一个岛屿.
这个题目有点难,主要是要通过100%的用例还是不好搞,是GG的。。。
"""
Definition for a point.
class Point:
def __init__(self, a=0, b=0):
self.x = a
self.y = b
"""
class Solution:
"""
@param n: An integer
@param m: An integer
@param operators: an array of point
@return: an integer array
"""
def numIslands2(self, n, m, operators):
# write your code here
self.father = {}
self.matrix = [[0] * m for i in range(n)]
for i in range(n):
for j in range(m):
self.father[(i, j)] = (i, j)
ans = []
self.cnt = 0
for p in operators:
a, b = p.x, p.y
if self.matrix[a][b]:
ans.append(self.cnt)
continue
self.matrix[a][b] = 1
self.cnt += 1
for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if 0 <= a + x < n and 0 <= b + y < m and self.matrix[a + x][b + y]:
f1 = self.find(a + x, b + y)
f2 = self.find(a, b)
if f1 != f2:
self.father[f1] = f2
self.cnt -= 1
ans.append(self.cnt)
return ans
def find(self, p1, p2):
a, b = p1, p2
path = []
while (a, b) != self.father[(a, b)]:
path.append((a, b))
a, b = self.father[(a, b)]
for p in path:
self.father[p] = (a, b)
return (a, b)
主要是细节处理,比较容易出错!!!
对于每一次操作(x, y), 如果(x, y)的上下左右都是0, 那么计数器加一; 如果不全为0, 则:
- 并查集查询其四周的1所属的集合, 假设它们属于 k 个不同的集合
- 计数器减去 k-1
- 将这 k 个集合, 连同 (x, y), 合并
178. 图是否是树
中文
English
给出 n
个节点,标号分别从 0
到 n - 1
并且给出一个 无向
边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
样例
样例 1:
输入: n = 5 edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
输出: true.
样例 2:
输入: n = 5 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]]
输出: false.
注意事项
你可以假设我们不会给出重复的边在边的列表当中. 无向
边 [0, 1]
和 [1, 0]
是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。
class Solution:
"""
@param n: An integer
@param edges: a list of undirected edges
@return: true if it's a valid tree, or false
"""
def validTree(self, n, edges):
if n - 1 != len(edges): # 这个是必须的,因为tree最终只能是n-1条变
return False
self.father = {i: i for i in range(n)}
self.size = n
for a, b in edges:
self.union(a, b)
return self.size == 1 #最终只能是一个孤岛
def union(self, a, b):
root_a = self.find(a)
root_b = self.find(b)
if root_a != root_b:
self.size -= 1
self.father[root_a] = root_b
def find(self, node):
path = []
while node != self.father[node]:
path.append(node)
node = self.father[node]
for n in path:
self.father[n] = node
return node
1070. 账户合并
中文
English
给定一个帐户列表,每个元素accounts [i]
是一个字符串列表,其中第一个元素accounts [i] [0]
是账户名称,其余元素是这个帐户的电子邮件。
现在,我们想合并这些帐户。
如果两个帐户有相同的电子邮件地址,则这两个帐户肯定属于同一个人。
请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为两个不同的人可能会使用相同的名称。
一个人可以拥有任意数量的账户,但他的所有帐户肯定具有相同的名称。
合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按字典序排序后的电子邮件。
帐户本身可以按任何顺序返回。
样例
样例 1:
输入:
[
["John", "johnsmith@mail.com", "john00@mail.com"],
["John", "johnnybravo@mail.com"],
["John", "johnsmith@mail.com", "john_newyork@mail.com"],
["Mary", "mary@mail.com"]
]
输出:
[
["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],
["John", "johnnybravo@mail.com"],
["Mary", "mary@mail.com"]
]
解释:
第一个第三个John是同一个人的账户,因为这两个账户有相同的邮箱:"johnsmith@mail.com".
剩下的两个账户分别是不同的人。因为他们没有和别的账户有相同的邮箱。
你可以以任意顺序返回结果。比如:
[
['Mary', 'mary@mail.com'],
['John', 'johnnybravo@mail.com'],
['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com']
]
也是可以的。
注意事项
账户个数在1~1000之间
每个账户下的电子邮件在1~10之间
每个字符串的长度在1~30之间
class Solution:
"""
@param accounts: List[List[str]]
@return: return a List[List[str]]
"""
def accountsMerge(self, accounts):
# write your code here
acc_dict = self.init(accounts)
clusters = self.join_accounts(acc_dict)
return self.merge(clusters, acc_dict)
def init(self, accounts):
acc_dict = {}
self.father = {}
for acc in accounts:
for i in range(1, len(acc)):
if acc[i] not in self.father:
self.father[acc[i]] = acc[1]
else: # detail CAUSTION
f1 = self.find(acc[i])
f2 = self.find(acc[1])
self.father[f1] = f2
acc_dict[acc[i]] = acc[0]
return acc_dict
def find(self, x):
path = []
while x != self.father[x]:
path.append(x)
x = self.father[x]
for p in path:
self.father[p] = x
return x
def join_accounts(self, acc_dict):
clusters = collections.defaultdict(set)
for acc in acc_dict:
f = self.find(acc)
clusters[f].add(acc)
return clusters
def merge(self, clusters, acc_dict):
ans = []
for c in clusters:
ans.append([acc_dict[c]] + sorted(list(clusters[c])))
return ans
有没有发现这种题目比较XXX!小细节处理易错,一次性100%通过用例比较难!
我自己处理的逻辑不清晰,应该用下面的方式!!!
1396. 集合合并
中文
English
有一个集合组成的list,如果有两个集合有相同的元素,将他们合并。返回最后还剩下几个集合。
样例
样例1:
输入:list = [[1,2,3],[3,9,7],[4,5,10]]
输出:2 .
样例:剩下[1,2,3,9,7]和[4,5,10]这2个集合。
样例 2:
输入:list = [[1],[1,2,3],[4],[8,7,4,5]]
输出 :2
解释:剩下[1,2,3]和[4,5,7,8] 2个集合。
注意事项
- 集合数 n <=
1000
。 - 每个集合的元素个数
m <= 100
。 - 元素一定是非负整数,且不大于
100000
。
class Solution:
"""
@param sets: Initial set list
@return: The final number of sets
"""
def setUnion(self, sets):
# Write your code here
self.init(sets)
self.connect(sets)
return self.get_clusters()
def init(self, sets):
self.data_set = set()
self.father = {}
for items in sets:
for x in items:
self.father[x] = x
self.data_set.add(x)
def connect(self, sets):
for items in sets:
for i in range(1, len(items)):
self.union(items[0], items[i]) # 关键!!!就按部就班来就好,不要去创新!!!
def get_clusters(self):
clusters = collections.defaultdict(set)
for x in self.data_set:
f = self.find(x)
clusters[f].add(x)
return len(clusters)
def union(self, x, y):
self.father[self.find(x)] = self.find(y)
def find(self, x):
path = []
while x != self.father[x]:
path.append(x)
x = self.father[x]
for p in path:
self.father[p] = x
return x
非常清晰和简单的逻辑,写起来代码就非常不容易出错了!!!关键就是找到union!!!