📅  最后修改于: 2023-12-03 14:58:33.935000             🧑  作者: Mango
给定一个 $n \times m$ 的矩阵,矩阵元素的值都是 0 或 1。如果矩阵中某个位置的值为 1,则说明该位置有门;否则,该位置没有门。你需要统计矩阵中有多少个独立的门。独立的门是指门之间没有相邻关系,即门之间不会共享公用的一圈。
def count_gates(matrix: List[List[int]]) -> int:
pass
参数 matrix
是一个 $n \times m$ 的矩阵,$n$ 和 $m$ 的取值范围都在 $[1, 100]$ 内。
返回值是一个整数,表示矩阵中独立的门的个数。
输入
[
[1, 0, 0, 0],
[0, 1, 1, 0],
[1, 0, 0, 1],
[0, 0, 0, 1]
]
输出
3
矩阵中一共有 4 扇门,其中前三个门之间没有相邻关系,最后一个门与前三个门共享了一圈,因此矩阵中有三个独立的门。
题目要求统计矩阵中有多少个独立的门,使得每个门之间都不共享公用的一圈。因此,我们可以采用深度优先搜索(DFS)的方法来遍历矩阵中的所有 1,每遍历到一个 1 就意味着发现了一个新门。
在 DFS 的过程中,我们需要判断当前门是否与之前发现的门共有边界(共享公用的一圈),从而避免重复计算门的个数。如何判断两个门之间是否共享边界?我们可以利用 BFS 的思想,从当前门出发,寻找距离它最近的其他门,如果两者之间的距离小于等于 1,则说明它们共享边界。
因此,我们可以分为以下几步来实现函数 count_gates:
算法的时间复杂度是 $O(n^4)$,其中 $n$ 表示矩阵的边长。每次 DFS 的时间复杂度是 $O(k)$,$k$ 表示当前门的大小,假设门平均大小为 $O(n^2)$,那么我们需要执行 $O(n^4)$ 次 DFS。
from typing import List
def count_gates(matrix: List[List[int]]) -> int:
def is_valid(x: int, y: int) -> bool:
return x >= 0 and x < n and y >= 0 and y < m
def dfs(x: int, y: int, gate_id: int):
if x < 0 or x >= n or y < 0 or y >= m:
return
if matrix[x][y] == 0 or visited[x][y] != 0:
return
visited[x][y] = gate_id
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if is_valid(nx, ny) and matrix[nx][ny] == 1 and visited[nx][ny] == 0:
if matrix[x][y] != matrix[nx][ny]:
shared_gates[visited[x][y]].add(visited[nx][ny])
shared_gates[visited[nx][ny]].add(visited[x][y])
else:
dfs(nx, ny, gate_id)
def bfs(x: int, y: int) -> int:
dist = [[n * m] * m for _ in range(n)]
dist[x][y] = 0
q = [(x, y)]
while q:
x, y = q.pop(0)
if (x, y) != (gates[gates_visited[-1]][0], gates[gates_visited[-1]][1]) and visited[x][y] != 0:
gates_visited.append(visited[x][y])
return dist[x][y]
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if is_valid(nx, ny) and matrix[nx][ny] == 1 and visited[nx][ny] == 0:
dist[nx][ny] = dist[x][y] + 1
q.append((nx, ny))
return n * m
n, m = len(matrix), len(matrix[0])
visited = [[0] * m for _ in range(n)]
gates = []
gates_visited = []
shared_gates = [set() for _ in range(n * m + 1)]
dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for i in range(n):
for j in range(m):
if matrix[i][j] == 1 and visited[i][j] == 0:
gates.append((i, j))
gates_visited.append(len(gates))
dfs(i, j, len(gates))
for i in range(len(gates)):
bfs(gates[i][0], gates[i][1])
return len(set(gates_visited) - shared_gates[0])
解释:
首先,在主函数中,我们定义了两个辅助函数:is_valid 和 dfs。
函数 is_valid 用于检测每次 DFS 搜索新位置时坐标的合法性;
函数 dfs 是我们的核心实现。它接收三个参数:当前坐标 x、y 和当前独立门的编号 gate_id,它的作用是以当前坐标为起点,遍历当前门所有的坐标点,并标记该门的边界。在遍历当前门的过程中,我们需要使用递归的 DFS 算法来遍历所有与当前点相邻的点。遍历的顺序为:向右、向左、向下、向上。当遍历完当前门的所有坐标点之后,我们需要将当前门的编号加入 visited 数组中,以便后续的 BFS 搜索。
接下来,我们在 count_gates 函数中定义了另外一个辅助函数 bfs。它接收两个参数:当前门的起始坐标 x 和 y,其返回值是起始坐标和最近邻门之间的距离。本函数的作用是以当前门为出发点,执行 BFS 算法来寻找当前门的最近邻门。
在 bfs 函数中,我们使用了一个二维数组 dist 来记录当前门到各个点的距离,初始化为 $n \times m$,即一个大于任何门之间距离的值。由于我们在寻找最近邻门的过程中,需要判断当前点是否为将当前门与其他门连接的点,因此我们需要在队列 q 中先加入起始坐标。每次取出队列中的一个坐标点 (x, y),并向它周围的点扩展。如果扩展到了其他门,则可以将当前门的编号加入 gates_visited 数组中,并返回当前门的起始坐标 $(x, y)$ 和最近邻门的距离。
在主函数中,我们首先对矩阵中的每个 1,执行一个 DFS,找出所有的门并给它们编号。在搜索的过程中,我们使用了 visited 数组来标记已经遍历过的位置,避免重复计算。
接下来,我们对于每个门,执行 BFS 算法,以遍历矩阵中所有点,计算出当前门到其他门之间的距离,并将得到的最近邻门编号保存在 gates_visited 数组中。在遍历矩阵中所有点时,我们需要判断当前点是否已被遍历过,以决定是否加入队列 q 中。我们在 BFS 之前,需要将 dist 数组中所有位置都初始化为一个大于每个门大小的值,以保证 BFS 始终能找到门的最近邻。
最后,我们计算矩阵中独立的门的个数。独立的门是指门之间没有相邻关系,即门之间不会共享公用的一圈。因此,我们可以遍历 gates_visited 数组中保存的所有门编号,使用一个 set 来统计独立的门的数量。使用 shared_gates 数组来保存每扇门与其他门共享边界的门编号,最后利用一个 set 来去重,得出矩阵中独立门的数量。