Leetcode刷题笔记63:图论3(1020. 飞地的数量-1254. 统计封闭岛屿的数目-130. 被围绕的区域)

116 阅读6分钟

导语

leetcode刷题笔记记录,主要记录题目包括:

这三道题目都可以使用dfs=或者bfs实现,与之前的问题唯一的不同点在于要区分处于边界的岛屿和海中的岛屿。一般的解题思路是将所有边界上的岛屿先全部遍历出来,进行标记。之后,只处理海洋中的岛屿即可完成题目。

Leetcode 1020. 飞地的数量

题目描述

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

 

示例 1:

输入: grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出: 3
解释: 有三个 10 包围。一个 1 没有被包围,因为它在边界上。

示例 2:

输入: grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出: 0
解释: 所有 1 都在边界上或可以到达边界。

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 500
  • grid[i][j] 的值为 0 或 1

解法

本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图的时候,统计此时还剩下的陆地就可以了。

dfs版本代码如下:

from typing import List

class Solution:
    def numEnclaves(self, grid: List[List[int]]) -> int:
        # 获取矩阵的行数和列数
        m, n = len(grid), len(grid[0])
        # 定义四个方向(左、右、上、下)
        directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]

        # 定义DFS函数
        def dfs(i, j):
            # 越界条件或者当前单元格不是陆地(即值不为1)时返回
            if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 1:
                return 
            else:
                # 标记当前陆地单元格为已访问(用2表示)
                grid[i][j] = 2
                # 递归访问上下左右相邻的单元格
                for dx, dy in directions:
                    dfs(i + dx, j + dy)

        # 遍历第一列和最后一列,如果发现陆地单元格(即值为1),则从该点开始进行DFS
        for i in range(m):
            dfs(i, 0)
            dfs(i, n - 1)

        # 遍历第一行和最后一行,如果发现陆地单元格(即值为1),则从该点开始进行DFS
        for j in range(n):
            dfs(0, j)
            dfs(m - 1, j)

        # 计算并返回矩阵中值为1的单元格数量(即无法离开网格的陆地单元格数量)
        ans = sum(cell == 1 for row in grid for cell in row)

        return ans

bfs版本代码如下:

from collections import deque
from typing import List  # 从typing模块导入List

class Solution:
    def numEnclaves(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])  # 获取网格的行数和列数
        directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]  # 定义四个可能的移动方向:上、下、左、右

        queue = deque()  # 使用deque(双端队列)作为队列

        # 找出所有在边界上的陆地单元格(值为1)并将它们加入队列
        for i in range(m):
            if grid[i][0] == 1:
                queue.append((i, 0))
            if grid[i][n-1] == 1:
                queue.append((i, n-1))
        for j in range(n):
            if grid[0][j] == 1:
                queue.append((0, j))
            if grid[m-1][j] == 1:
                queue.append((m-1, j))

        # 使用BFS来标记所有从边界上的陆地单元格可以到达的陆地单元格
        while queue:
            x, y = queue.popleft()  # 从队列前端删除一个元素并返回它
            if grid[x][y] == 1:  # 如果这个单元格是未标记的陆地
                grid[x][y] = 2  # 标记为已访问(用2表示)

                # 检查相邻的单元格,如果它们是陆地,则加入队列
                for dx, dy in directions:
                    new_x, new_y = x + dx, y + dy
                    if 0 <= new_x < m and 0 <= new_y < n and grid[new_x][new_y] == 1:
                        queue.append((new_x, new_y))

        # 计算所有未被标记(即未被从边界上的陆地单元格访问)的陆地单元格数量
        ans = sum(cell == 1 for row in grid for cell in row)

        return ans  # 返回结果

Leetcode 1254. 统计封闭岛屿的数目

题目描述

二维矩阵 grid 由 0 (土地)和 1 (水)组成。岛是由最大的4个方向连通的 0 组成的群,封闭岛是一个 完全 由1包围(左、上、右、下)的岛。

请返回 封闭岛屿 的数目。

 

示例 1:

输入: grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
输出: 2
解释:
灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。

示例 2:

输入: grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]]
输出: 1

示例 3:

输入: grid = [[1,1,1,1,1,1,1],
             [1,0,0,0,0,0,1],
             [1,0,1,1,1,0,1],
             [1,0,1,0,1,0,1],
             [1,0,1,1,1,0,1],
             [1,0,0,0,0,0,1],
             [1,1,1,1,1,1,1]]
输出: 2

 

提示:

  • 1 <= grid.length, grid[0].length <= 100
  • 0 <= grid[i][j] <=1

解法

这道题目与上一道题目类似,但最终求解的是海洋中岛屿的数目,因此在处理完边界岛屿之后的逻辑稍有不同,同时,需要注意的是这里0和1的含义刚好反过来了,

具体dfs代码如下:

from typing import List

class Solution:
    def closedIsland(self, grid: List[List[int]]) -> int:
        # 获取矩阵的行数和列数
        m, n = len(grid), len(grid[0])
        
        # 初始化封闭岛屿的计数器
        ans = 0
        
        # 定义四个方向(上,下,左,右)
        directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]

        # 定义DFS函数,用于深度优先遍历,并将访问过的土地标记为2
        def dfs(i, j):
            # 超出边界或者不是土地(即为水或者已经访问过的土地)则返回
            if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != 0:
                return
            else:
                grid[i][j] = 2  # 标记为已访问
                # 遍历当前土地的四个方向
                for dx, dy in directions:
                    dfs(i + dx, j + dy)
        
        # 首先标记与边界土地相连通的土地
        for i in range(m):
            dfs(i, 0)
            dfs(i, n - 1)
        for j in range(n):
            dfs(0, j)
            dfs(m - 1, j)

        # 遍历整个矩阵,找到剩余的封闭岛屿并进行标记
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    ans += 1  # 每找到一个新的封闭岛屿,计数器加1
                    dfs(i, j)

        return ans  # 返回封闭岛屿的数量

bfs代码如下:

from collections import deque
from typing import List

class Solution:
    def closedIsland(self, grid: List[List[int]]) -> int:
        # 获取矩阵的行数和列数
        m, n = len(grid), len(grid[0])
        
        # 初始化封闭岛屿的计数器
        ans = 0
        
        # 定义四个方向(上,下,左,右)
        directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
        
        # 创建一个双端队列用于存储将要访问的坐标
        queue = deque()
        
        # 将边界上的0(土地)加入到队列中
        for i in range(m):
            if grid[i][0] == 0:
                queue.append((i, 0))
            if grid[i][n-1] == 0:
                queue.append((i, n-1))
        for j in range(n):
            if grid[0][j] == 0:
                queue.append((0, j))
            if grid[m-1][j] == 0:
                queue.append((m-1, j))

        # bfs函数用于广度优先遍历,并将访问过的土地标记为2
        def bfs(queue):
            while queue:
                x, y = queue.popleft()
                if 0 <= x < m and 0 <= y < n and grid[x][y] == 0:
                    grid[x][y] = 2
                    for dx, dy in directions:
                        queue.append((x + dx, y + dy))
        
        # 先标记和边界连接的土地
        bfs(queue)
        
        # 遍历整个矩阵,找到剩余的封闭岛屿并进行标记
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    queue = deque([(i, j)])
                    ans += 1  # 每找到一个新的封闭岛屿,计数器加1
                    bfs(queue)
        
        return ans  # 返回封闭岛屿的数量

Leetcode 130. 被围绕的区域

题目描述

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

 

示例 1:

输入: board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出: [["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释: 被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2:

输入: board = [["X"]]
输出: [["X"]]

 

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] 为 'X' 或 'O'

解法

这道题目的套路与上两道题目类似,我们首先需要将边界上的岛屿进行标记,这里我将所有边界岛屿标记为‘A’,之后,将所有剩下的‘O’(即题目中描述的海洋中的孤岛)替换为‘X’,将'A'替换为‘O’即可,使用bfs和dfs的作用就是处理这些边界岛屿。

dfs代码如下:

from typing import List

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 获取矩阵的行数和列数
        m, n = len(board), len(board[0])
        
        # 定义用于遍历的四个方向(上、下、左、右)
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        # 定义DFS函数,用于标记与边界'O'相连通的'O'
        def dfs(i, j):
            # 越界或者不是'O'则返回
            if i < 0 or i >= m or j < 0 or j >= n or board[i][j] != 'O':
                return
            else:
                # 将与边界'O'相连的'O'暂时标记为'A'
                board[i][j] = 'A'
                
                # 遍历当前位置的四个方向
                for dx, dy in directions:
                    dfs(i + dx, j + dy)

        # 首先标记与边界'O'相连通的'O'
        for i in range(m):
            dfs(i, 0)
            dfs(i, n - 1)
        for j in range(n):
            dfs(0, j)
            dfs(m - 1, j)

        # 重新遍历矩阵,将标记为'A'的还原为'O',未标记的'O'(即被'X'包围的)改为'X'
        for i in range(m):
            for j in range(n):
                if board[i][j] == 'A':
                    board[i][j] = 'O'
                elif board[i][j] == 'O':
                    board[i][j] = 'X'
        
        # 由于函数要求修改原地,所以不需要返回值
        return

bfs代码如下:

from typing import List
from collections import deque

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        # 获取矩阵的行数和列数
        m, n = len(board), len(board[0])

        # 初始化一个队列,用于BFS
        queue = deque()
        
        # 定义用于遍历的四个方向(上、下、左、右)
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        # 将边界上的'O'都添加到队列中,作为BFS的起点
        for i in range(m):
            if board[i][0] == "O":
                queue.append((i, 0))
            if board[i][n-1] == "O":
                queue.append((i, n-1))
        for j in range(n):
            if board[0][j] == "O":
                queue.append((0, j))
            if board[m-1][j] == "O":
                queue.append((m-1, j))

        # 定义BFS函数
        def bfs(queue):
            while queue:
                x, y = queue.popleft()
                # 只处理值为'O'的点
                if board[x][y] == "O":
                    # 将与边界'O'相连通的'O'暂时标记为'A'
                    board[x][y] = "A"
                    # 遍历当前点的四个方向
                    for dx, dy in directions:
                        new_x, new_y = x + dx, y + dy
                        if 0 <= new_x < m and 0 <= new_y < n and board[new_x][new_y] == "O":
                            queue.append((new_x, new_y))
        
        # 执行BFS,找出所有与边界'O'相连的'O',并标记为'A'
        bfs(queue)

        # 遍历整个矩阵
        for i in range(m):
            for j in range(n):
                # 将标记为'A'的还原为'O'
                if board[i][j] == 'A':
                    board[i][j] = 'O'
                # 未标记的'O'(即被'X'包围的)改为'X'
                elif board[i][j] == "O":
                    board[i][j] = "X"

        return board