📌  相关文章
📜  门| Sudo GATE 2020 Mock II(2019 年 1 月 10 日)|第 65 题(1)

📅  最后修改于: 2023-12-03 14:58:33.935000             🧑  作者: Mango

Sudo GATE 2020 Mock II(2019 年 1 月 10 日)|第 65 题

题目描述

给定一个 $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:

  1. 遍历矩阵中的所有 1,每遍历到一个 1 就开启一个新门,否则继续搜索门的边界;
  2. 在遍历到新门时,以当前门为起点,执行 BFS,寻找当前门的最近邻门;
  3. 以当前门为起点,执行 DFS,遍历当前门所有的坐标点,并标记该门的边界;
  4. 重复执行以上操作,直到遍历完矩阵中的所有 1。
时间复杂度

算法的时间复杂度是 $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 来去重,得出矩阵中独立门的数量。