leetcode&lintcode分类刷题:图论(一、连通域/岛屿问题)

1、本次总结的题目通常是在二维矩阵考察连通域/岛屿问题 ,常用的方法包括深度优先搜索、广度优先搜索和并查集,根据具体的题目可以选择最合适的方法,我个人优选在逻辑思维上简单直观的广度优先搜索 方法

2、二维矩阵考察连通域/岛屿问题 ,包括简单的连通域染色、岛屿数量、飞地数量、岛屿面积等,复杂一点的题目考察对每个连通域/岛屿如何更好地标记,比如最大人工岛(简单的数字标记)、岛屿形状(相对位置连起来的元组或字符串标记)等

733. 图像渲染

本题是连通域染色问题的基础题目 ,是深度优先搜索或广度优先搜索的基本练习题,需要注意不用染色的特殊情况,比较细节

python 复制代码
from typing import List
from collections import deque
'''
733. 图像渲染
有一幅以m x n的二维整数数组表示的图画image,其中image[i][j]表示该图画的像素值大小。
你也被给予三个整数 sr , sc 和 newColor 。你应该从像素image[sr][sc]开始对图像进行 上色填充 。
为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,
接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,......,重复该过程。
将所有有记录的像素点的颜色值改为newColor。
最后返回 经过上色渲染后的图像。
示例 1:
    输入: image = [[1,1,1],[1,1,0],[1,0,1]],sr = 1, sc = 1, newColor = 2
    输出: [[2,2,2],[2,2,0],[2,0,1]]
    解析: 在图像的正中间,(坐标(sr,sc)=(1,1)),在路径上所有符合条件的像素点的颜色都被更改成2。
    注意,右下角的像素没有更改为2,因为它不是在上下左右四个方向上与初始点相连的像素点。
题眼:连通域染色问题,DFS、BFS的基础操作
'''


class Solution:
    def floodFill(self, image: List[List[int]], sr: int, sc: int, color: int) -> List[List[int]]:
        # 思路1、深度优先搜索
        # 情况1、矩阵为空
        if not image:
            return image
        m, n = len(image), len(image[0])
        val = image[sr][sc]
        # 情况2、矩阵不用染色
        if val == color:
            return image
        # 情况3、深度优先搜索
        # 1、定义递归函数
        def dfs(x, y):
            # 2、终止条件
            if not 0 <= x < m or not 0 <= y < n or image[x][y] != val:  # 不合法&非连通 直接返回
                return
            # 3、递归操作
            image[x][y] = color
            dfs(x+1, y)
            dfs(x-1, y)
            dfs(x, y+1)
            dfs(x, y-1)
        dfs(sr, sc)
        return image

        # # 思路2、广度优先搜索
        # # 情况1、矩阵为空
        # if not image:
        #     return image
        # m, n = len(image), len(image[0])
        # val = image[sr][sc]
        # # 情况2、矩阵不用染色
        # if val == color:
        #     return image
        # 情况3、广度优先搜索
        # que = deque()
        # que.append((sr, sc))
        # image[sr][sc] = color  # 入队立刻染色
        # while que:
        #     x, y = que.popleft()
        #     for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):  # 沿 4个方向 搜索
        #         if 0 <= tx < m and 0 <= ty < n and image[tx][ty] == val:  # 合法&连通 才入队
        #             que.append((tx, ty))
        #             image[tx][ty] = color  # 入队立刻染色
        # return image


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            in_line1 = in_line[1].strip().split('s')[0].strip()[1: -2]
            image = []
            for row in in_line1.split(']')[: -1]:
                image.append([int(i) for i in row.split('[')[1].split(',')])
            sr = int(in_line[2].strip().split(',')[0])
            sc = int(in_line[3].strip().split(',')[0])
            newColor = int(in_line[4].strip())
            print(obj.floodFill(image, sr, sc, newColor))
        except EOFError:
            break

1034. 边界着色

"733. 图像渲染"的扩展,理解题很重要 :只对连通域的边界位置的方块着色,可以把边界方块放到一个列表中,遍历完矩阵后再染色

python 复制代码
from typing import List
from collections import deque
'''
1034. 边界着色
给你一个大小为 m x n 的整数矩阵 grid ,表示一个网格。另给你三个整数row、col 和 color 。网格中的每个值表示该位置处的网格块的颜色。
两个网格块属于同一 连通分量 需满足下述全部条件:
    两个网格块颜色相同
    在上、下、左、右任意一个方向上相邻
连通分量的边界 是指连通分量中满足下述条件之一的所有网格块:
    在上、下、左、右任意一个方向上与不属于同一连通分量的网格块相邻
    在网格的边界上(第一行/列或最后一行/列)
请你使用指定颜色color 为所有包含网格块grid[row][col] 的 连通分量的边界 进行着色,并返回最终的网格grid 。
示例 1:
    输入:grid = [[1,1],[1,2]], row = 0, col = 0, color = 3
    输出:[[3,3],[3,2]]
题眼:"733. 图像渲染"的扩展,理解题很重要:只对连通域的边界方块着色,可以把边界方块放到一个列表中
思路:第一步,找到 "连通区域"的边界方块,并把它保存起来
     第二步,对保存的边界方块进行着色
细节:如果color==grid[row][col],则不必着色,更不必寻找 "连通区域"的边界
'''


class Solution:
    def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
        # 理解题很重要:只对边界区域着色,可以把边界位置放到一个列表中
        # 思路1、广度优先遍历
        # # 情况1、矩阵为空
        # if not grid:
        #     return []
        # # 情况2、不需要着色
        # if grid[row][col] == color:
        #     return grid
        # # 情况3、需要着色
        # que = deque()
        # m, n = len(grid), len(grid[0])
        # visited = [[False] * n for _ in range(m)]
        # que.append((row, col))
        # visited[row][col] = True  # 入队就标记
        # target = grid[row][col]
        # border = []
        # while len(que) > 0:
        #     x, y = que.popleft()
        #     # 对连通域的每个块判断是否为边界
        #     if x == 0 or x == m-1 or y == 0 or y == n-1:
        #         border.append((x, y))
        #     elif grid[x+1][y] != target or grid[x-1][y] != target or grid[x][y+1] != target or grid[x][y-1] != target:
        #         border.append((x, y))
        #     for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
        #         if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == target:
        #             que.append((tx, ty))
        #             visited[tx][ty] = True
        # for i, j in border:
        #     grid[i][j] = color
        # return grid


        # 思路2、深度优先遍历
        # 情况1、矩阵为空
        if not grid:
            return []
        # 情况2、不需要着色
        if grid[row][col] == color:
            return grid
        # 情况3、需要着色
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]
        target = grid[row][col]
        border = []
        # 1、定义递归函数
        def dfs(x: int, y: int):
            # 2、终止条件
            if not 0 <= x < m or not 0 <= y < n or visited[x][y] or grid[x][y] != target:
                return
            # 3、递归操作
            visited[x][y] = True
            if x == 0 or x == m-1 or y == 0 or y == n-1:
                border.append((x, y))
            elif grid[x+1][y] != target or grid[x-1][y] != target or grid[x][y+1] != target or grid[x][y-1] != target:
                border.append(((x, y)))
            dfs(x+1, y)
            dfs(x-1, y)
            dfs(x, y+1)
            dfs(x, y-1)
        dfs(row, col)
        for i, j in border:
            grid[i][j] = color
        return grid


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            grid = []
            if in_line[1].strip().split('r')[0].strip()[1: -2] != '':
                for row in in_line[1].strip().split('r')[0].strip()[1: -2].split(']')[:-1]:
                    grid.append([int(n) for n in row.split('[')[1].split(',')])
            row = int(in_line[2].split(',')[0])
            col = int(in_line[3].split(',')[0])
            color = int(in_line[4])
            print(obj.colorBorder(grid, row, col, color))
        except EOFError:
            break

200. 岛屿数量

1、岛屿数量问题的基础题目,也是考察连通域的个数,不像前面两道题一样给定起始的搜索位置了,需要对二维矩阵从首位置遍历到末位置进行搜索了,推荐简单直观的广度优先搜索方法

2、注意细节就是入队的位置及时标记,否则可能会超时

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
200. 岛屿数量
给你一个由'1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
    输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]
    输出:1
模板:广搜或深搜都可以解决;只要能把相邻且相同属性的节点标记上就行。
题眼:连通域个数问题,DFS、BFS的基础操作
思路:采用visited矩阵标记访问过的位置,可以避免原矩阵被修改!
'''


class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 思路1、广度优先搜索
        # 特殊情况:区域为空
        if not grid:
            return 0
        result = 0
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]  # 标记矩阵
        que = deque()
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '1' and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True  # 注意:必须立刻标记,否则会在后续有太多重复判断,会超时
                    while len(que) > 0:
                        x, y = que.popleft()
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == '1':
                                que.append((tx, ty))
                                visited[tx][ty] = True
                    result += 1
        return result
        # # 思路2、深度优先搜索
        # # 特殊情况:区域为空
        # if not grid:
        #     return 0
        # result = 0
        # m, n = len(grid), len(grid[0])
        # visited = [[False] * n for _ in range(m)]  # 标记矩阵
        # # 1、定义递归函数
        # def dfs(x: int, y: int):
        #     # 2、终止条件
        #     if not 0 <= x < m or not 0 <= y < n or visited[x][y] or grid[x][y] != '1':
        #         return
        #     # 3、递归操作
        #     visited[x][y] = True
        #     dfs(x+1, y)
        #     dfs(x-1, y)
        #     dfs(x, y+1)
        #     dfs(x, y-1)
        # for i in range(m):
        #     for j in range(n):
        #         if grid[i][j] == '1' and not visited[i][j]:  # 注意:不可以在调用前进行标记处理,这样就无法实现dfs()递归
        #             dfs(i, j)
        #             result += 1
        # return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')[1].strip()[1: -1]
            grid = []
            for row in in_line.split(']')[: -1]:
                ans = []
                for s in row.split('[')[1].split(','):
                    ans.append(s[1: -1])
                grid.append(ans)
            obj.numIslands(grid)
            print(grid)

        except EOFError:
            break

1020. 飞地的数量

"200. 岛屿数量"的扩展,正常广度优先搜索方法基础上,额外添加 连通域内方块个数统计及连通域是否为飞地的判断即可------只需要一次遍历即可

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
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
    解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
题眼:"200. 岛屿数量"的扩展,正常BFS遍历添加上 连通域内方块个数及连通域是否为飞地的判断即可------只需要一次遍历即可
按照这个思路改成DFS时,改不出来,我太菜了!
'''


class Solution:
    def numEnclaves(self, grid: List[List[int]]) -> int:
        # 思路1、广度优先搜索
        # 情况1、矩阵为空
        if not grid:
            return 0
        # 情况2、矩阵不为空
        que = deque()
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]
        result = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1 and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    temp_num = 1  # 记录连通域内单元格数量
                    isEnclave = True  # 标记连通域是否为飞地
                    while len(que) > 0:
                        x, y = que.popleft()
                        if isEnclave:
                            if x == 0 or x == m-1 or y == 0 or y == n-1:
                                isEnclave = False
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == 1:
                                que.append((tx, ty))
                                visited[tx][ty] = True
                                temp_num += 1
                    if isEnclave:
                        result += temp_num
        return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')[1].strip()[1: -1]
            grid = []
            for row in in_line.split(']')[: -1]:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            print(obj.numEnclaves(grid))
        except EOFError:
            break

1254. 统计封闭岛屿的数目

"1020. 飞地的数量"一样的题意,正常广度优先搜索方法基础上,额外添加 连通域是否为飞地的判断即可

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
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 区域包围)。
题眼:和"1020. 飞地的数量"是完全一样的
思路:第一步、正常访问并跟踪grid的岛屿
     第二步、判断跟踪的岛屿是否是 封闭的:用一个标记去跟踪遍历的岛屿(只要连通域没有在边界上即可),但是一定要把整个岛屿遍历完,不能加break提前跳出
'''


class Solution:
    def closedIsland(self, grid: List[List[int]]) -> int:
        # 这道题目就是飞地的数量
        result = 0
        que = deque()
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0 and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    isClose = True
                    while len(que) > 0:
                        x, y = que.popleft()
                        if isClose:
                            if x == 0 or x == m-1 or y == 0 or y == n-1:
                                isClose = False
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == 0:
                                que.append((tx, ty))
                                visited[tx][ty] = True
                    if isClose:
                        result += 1
        return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            grid = []
            for row in in_line[1].strip()[1: -1].split(']')[: -1]:
                grid.append([int(n) for n in row.split('[')[1].split(',')])
            print(obj.closedIsland(grid))
        except EOFError:
            break

130. 被围绕的区域

"1020. 飞地的数量"一样的题意,正常广度优先搜索方法基础上,额外添加 连通域内方块都预存及连通域是否为飞地的判断即可------只需要一次遍历即可

python 复制代码
import collections
from typing import List, Optional, Union
from collections import deque
'''
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'。
    如果两个元素在水平或垂直方向相邻,则称它们是"相连"的。
题眼:"1020. 飞地的数量"一样的题意,正常BFS遍历,添加上 连通域内方块都预存及连通域是否为飞地的判断即可------只需要一次遍历即可
按照这个思路改成DFS时,还是改不出来,我太菜了!
'''


class Solution:
    def solve(self, board: List[List[str]]) -> None:
        # 跟"飞地的数量"是一样的题意
        # 用擅长的广度优先算法做吧
        # 情况1、矩阵为空
        if not board:
            return
        # 情况2、矩阵不为空
        que = deque()
        m, n = len(board), len(board[0])
        visited = [[False] * n for _ in range(m)]
        result = []
        for i in range(m):
            for j in range(n):
                if board[i][j] == 'O' and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    temp = []  # 将连通域内的所有节点都记录下来
                    isAdd = True  # 默认连通域是飞地
                    while len(que) > 0:
                        x, y = que.popleft()
                        temp.append((x, y))
                        if isAdd:
                            if x == 0 or x == m-1 or y == 0 or y == n-1:
                                isAdd = False
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and board[tx][ty] == 'O':
                                que.append((tx, ty))
                                visited[tx][ty] = True
                    if isAdd:
                        result.append(temp)
        for temp in result:
            for x, y in temp:
                board[x][y] = 'X'


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')[1].strip()[1: -1]
            board = []
            for row in in_line.split(']')[: -1]:
                ans = []
                for s in row.split('[')[1].split(','):
                    ans.append(s[1: -1])
                board.append(ans)
            obj.solve(board)
            print(board)

        except EOFError:
            break

1905. 统计子岛屿

有点类似于飞地("1020. 飞地的数量")的判断,读懂题很重要:grid2的岛屿,对应到grid1位置都为1的话,说明就是个grid1的子岛屿,正常广度优先搜索方法基础上,额外添加 对应位置到grid1中是否也都为1的判断即可

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
1905. 统计子岛屿
给你两个m x n的二进制矩阵grid1 和grid2,它们只包含0(表示水域)和 1(表示陆地)。一个 岛屿是由 四个方向(水平或者竖直)上相邻的1组成的区域。
任何矩阵以外的区域都视为水域。如果 grid2的一个岛屿,被 grid1的一个岛屿完全 包含,也就是说 grid2中该岛屿的每一个格子都被 grid1中同一个岛屿完全包含,
那么我们称 grid2中的这个岛屿为 子岛屿。请你返回 grid2中 子岛屿的 数目。
示例 1:
    输入:grid1 = [[1,1,1,0,0],[0,1,1,1,1],[0,0,0,0,0],[1,0,0,0,0],[1,1,0,1,1]], grid2 = [[1,1,1,0,0],[0,0,1,1,1],[0,1,0,0,0],[1,0,1,1,0],[0,1,0,1,0]]
    输出:3
    解释:如上图所示,左边为 grid1 ,右边为 grid2 。
    grid2 中标红的 1 区域是子岛屿,总共有 3 个子岛屿。
题眼:读懂题很重要:grid2的岛屿,对应到grid1位置都为1的话,说明就是个grid1的子岛屿;从这个角度看,有点类似于飞地("1020. 飞地的数量")的判断了
思路:第一步、正常访问并跟踪grid2的岛屿
     第二步、用一个标记判断跟踪的岛屿是否是 子岛屿(grid1对应位置处都是陆地就是子岛屿:gird2岛屿的正常遍历确保grid1的对应位置是一个岛屿),
     但是一定要把整个岛屿遍历完,不能加break提前跳出(存在可能使得剩余没有遍历的位置构成满足条件的子岛屿了)
'''


class Solution:
    def countSubIslands(self, grid1: List[List[int]], grid2: List[List[int]]) -> int:
        # 读懂题很重要:grid2的岛屿,对应到grid1种位置都为1的话,说明就是个grid1的子岛屿
        # 从这个角度看,有点类似于飞地的判断了
        result = 0
        que = deque()
        m, n = len(grid2), len(grid2[0])
        visited = [[False] * n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if grid2[i][j] == 1 and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    isSub = True
                    while len(que) > 0:
                        x, y = que.popleft()
                        if isSub:
                            if grid1[x][y] == 0:
                                isSub = False
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid2[tx][ty] == 1:
                                que.append((tx, ty))
                                visited[tx][ty] = True
                    if isSub:
                        result += 1
        return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')
            grid1, grid2 = [], []
            for row in in_line[1].strip().split('g')[0][1: -3].split(']')[: -1]:
                grid1.append([int(n) for n in row.split('[')[1].split(',')])
            for row in in_line[2].strip()[1: -1].split(']')[: -1]:
                grid2.append([int(n) for n in row.split('[')[1].split(',')])
            print(obj.countSubIslands(grid1, grid2))
        except EOFError:
            break

695. 岛屿的最大面积

"200. 岛屿数量"的扩展,求解"连通域"的最大面积,正常广度优先搜索方法基础上,额外添加 连通域内方块个数的累计即可------只需要一次遍历即可

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
695. 岛屿的最大面积
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿是由一些相邻的1(代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 
示例 1:
    输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
    输出:6
    解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
题眼:"200. 岛屿数量"的扩展,求解"连通域"的最大面积,正常BFS遍历,添加上 连通域内方块个数的累计即可------只需要一次遍历即可
按照这个思路改成DFS时,觉得还是不那么好理解!
'''


class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        # 广度优先搜索,在有返回值的时候,可能更好理解
        # 情况1、矩阵为空
        if not grid:
            return 0
        # 情况2、矩阵不为空
        que = deque()
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]
        result = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1 and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    temp = 1
                    while len(que) > 0:
                        x, y = que.popleft()
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == 1:
                                que.append((tx, ty))
                                visited[tx][ty] = True
                                temp += 1
                    result = max(result, temp)
        return result

        # # 深度优先搜索,在有返回值的时候,感觉并不是特别理解
        # # 特殊情况:矩阵为空
        # if not grid:
        #     return 0
        # m, n = len(grid), len(grid[0])
        # visited = [[False] * n for _ in range(m)]
        # maxArea = 0
        # # 搜索函数
        # def dfs(x, y):
        #     if not 0 <= x < m or not 0 <= y < n or visited[x][y] or grid[x][y] != 1:  # 非法访问、重复访问、水体
        #         return 0
        #     visited[x][y] = True
        #     # ans = 1
        #     n1 = dfs(x + 1, y)
        #     n2 = dfs(x - 1, y)
        #     n3 = dfs(x, y - 1)
        #     n4 = dfs(x, y + 1)
        #     return 1 + n1 + n2 + n3 + n4
        # for i in range(m):
        #     for j in range(n):
        #         if grid[i][j] == 1 and visited[i][j] == False:  # 遇到岛屿,并且是第一次访问
        #             area = dfs(i, j)
        #             maxArea = max(maxArea, area)
        # return maxArea


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')[1].strip()[1: -1]
            grid = []
            for row in in_line.split(']')[:-1]:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            print(obj.maxAreaOfIsland(grid))
        except EOFError:
            break

677 · 大岛的数量

与"695. 岛屿的最大面积"差不多的一道题目,正常广度优先搜索方法基础上,额外添加 连通域内方块个数的累计即可------只需要一次遍历即可

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
lintcode
677 · 大岛的数量
给一个布尔类型的二维数组, 0 表示海, 1 表示岛。如果两个1是相邻的,那么我们认为他们是同一个岛.我们只考虑 上下左右 相邻.
找到大小在 k 及 k 以上的岛屿的数量
示例 1:
    输入:
    [[1,1,0,0,0],[0,1,0,0,1],[0,0,0,1,1],[0,0,0,0,0],[0,0,0,0,1]]
    2
    输出: 2
    解释:
    2D网络为
    [[1, 1, 0, 0, 0],
     [0, 1, 0, 0, 1],
     [0, 0, 0, 1, 1],
     [0, 0, 0, 0, 0],
     [0, 0, 0, 0, 1]]
    一共有两个大小为3的岛。
题眼:从这题 开始 统一用BFS做吧,因为DFS可能存在栈溢出,同时, 最短路径问题 也是用BFS做的,统一模板,方便记忆
思路: 
'''


class Solution:
    def numsof_island(self, grid: List[List[int]], k: int) -> int:
        # 特殊情况
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        dq = deque()  # 用BFS做
        result = 0
        visited = [[False]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1 and not visited[i][j]:  # 第一次被访问的岛屿
                    dq.append((i, j))  # 加入队列,并立刻标记
                    visited[i][j] = True
                    curArea = 1
                    while dq:  # BFS
                        x, y = dq.popleft()
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and grid[tx][ty] == 1 and not visited[tx][ty]:  # 有效、是岛屿、第一次访问
                                dq.append((tx, ty))  # 加入队列,并立刻标记
                                visited[tx][ty] = True
                                curArea += 1
                    if curArea >= k:
                        result += 1
        return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip()[1: -1].split(']')[: -1]
            grid = []
            for row in in_line:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            k = int(input().strip())
            print(obj.numsof_island(grid, k))
        except EOFError:
            break

827. 最大人工岛

"695. 岛屿的最大面积"再加上 0变1 后的最大面积判断:需要额外添加 简单数字的岛屿标记矩阵及岛屿标记-面积哈希表

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
827. 最大人工岛
给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格0 变成1 。
返回执行此操作后,grid 中最大的岛屿面积是多少?
岛屿 由一组上、下、左、右四个方向相连的1 形成。
示例 1:
    输入: grid = [[1, 0], [0, 1]]
    输出: 3
    解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。
题眼:"695. 岛屿的最大面积"再加上 0变1 后的最大面积判断:需要额外添加 简单数字的岛屿标记矩阵及岛屿标记-面积哈希表
思路:第一步,求出 岛屿面积 并对岛屿进行简单数字的标记(与哈希表的key值相同,便于直接取出value值,而不是再for循环一次),建立 岛屿标记-面积 哈希字典存储
     第二步,遍历0,将其变为1后,求能连接的最大岛屿面积 
'''


class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        # 需要两次遍历,一次获取所有岛屿及其面积,二次对0变1看能连接几个岛屿------加一个岛屿标记矩阵能够在不改变原矩阵的情况下不超时
        # 当然,这个题直接在原矩阵中标记是最方便的,还可以省去visited矩阵
        # 特殊情况
        if not grid:
            return 0
        que = deque()
        m, n = len(grid), len(grid[0])
        visited = [[False] * n for _ in range(m)]
        marked = [[-1] * n for _ in range(m)]
        mark_num = 0
        zeros = []
        islands = {}
        result = 0  # 在一次遍历中成为单个最大的岛屿
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    zeros.append((i, j))
                elif grid[i][j] == 1 and not visited[i][j]:
                    que.append((i, j))
                    visited[i][j] = True
                    marked[i][j] = mark_num
                    temp = 1  # 临时变量,统计当前岛屿面积
                    while len(que) > 0:
                        x, y = que.popleft()
                        for tx, ty in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
                            if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == 1:
                                que.append((tx, ty))
                                visited[tx][ty] = True
                                marked[tx][ty] = mark_num
                                temp += 1
                    islands[mark_num] = temp
                    mark_num += 1
                    result = max(result, temp)
        # 二次对0变1看能连接几个岛屿
        for x, y in zeros:
            match_keys = set()  # 避免加入重复的key
            for tx, ty in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
                if 0 <= tx < m and 0 <= ty < n and marked[tx][ty] in islands:
                    match_keys.add(marked[tx][ty])
            temp = 1  # 细节:加上自己,初始值设置为1
            for k in match_keys:
                temp += islands[k]
            result = max(result, temp)
        return result

        # 超时!
        # # 需要两次遍历,一次获取所有岛屿及其面积,二次对0变1看能连接几个岛屿
        # # 特殊情况
        # if not grid:
        #     return 0
        # que = deque()
        # m, n = len(grid), len(grid[0])
        # visited = [[False] * n for _ in range(m)]
        # zeros = []
        # islands = {}
        # result = 0  # 在一次遍历中成为单个最大的岛屿
        # for i in range(m):
        #     for j in range(n):
        #         if grid[i][j] == 0:
        #             zeros.append((i, j))
        #         elif grid[i][j] == 1 and not visited[i][j]:
        #             que.append((i, j))
        #             visited[i][j] = True
        #             key = [(i, j)]
        #             while len(que) > 0:
        #                 x, y = que.popleft()
        #                 for tx, ty in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
        #                     if 0 <= tx < m and 0 <= ty < n and not visited[tx][ty] and grid[tx][ty] == 1:
        #                         que.append((tx, ty))
        #                         visited[tx][ty] = True
        #                         key.append((tx, ty))
        #             islands[tuple(key)] = len(key)
        #             result = max(result, len(key))
        # # 二次对0变1看能连接几个岛屿
        # for x, y in zeros:
        #     match_keys = set()  # 避免加入重复的key
        #     for tx, ty in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
        #         if 0 <= tx < m and 0 <= ty < n:
        #             for key in islands:  # 这里再套for循环取对应的岛屿标记,使得最后答案超时了
        #                 if (tx, ty) in key:
        #                     match_keys.add(key)
        #                     break
        #     temp = 1
        #     for k in match_keys:
        #         temp += islands[k]
        #     result = max(result, temp)
        # return result


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip().split('=')[1].strip()[1: -1]
            grid = []
            for row in in_line.split(']')[: -1]:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            print(obj.largestIsland(grid))
        except EOFError:
            break

860 · 不同岛屿的个数

需要在判断岛屿个数的基础上,把相同形状的岛屿去重 ,也就是需要在遍历岛屿的基础上,增加hashset标记路径相对岛屿起始点的路径:因为对矩阵的遍历总是从左上到右下的,因此相同形状的岛屿起点总是位于左上角,添加的相对路径也能对应相等,最终hashset就是岛屿个数

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
lintcode
860 · 不同岛屿的个数
给定一个由0和1组成的非空的二维网格,一个岛屿是指四个方向(包括横向和纵向)都相连的一组1(1表示陆地)。你可以假设网格的四个边缘都被水包围。
找出所有不同的岛屿的个数。如果一个岛屿与另一个岛屿形状相同(不考虑旋转和翻折),我们认为这两个岛屿是相同的。
示例 1:
    输入:
    [[1,1,0,0,0],[1,1,0,0,0],[0,0,0,1,1],[0,0,0,1,1]]
    输出:1
    解释:一共有两个岛,但是两个岛的形状相同
    输入:[[1,1,0,0,1],[1,0,0,0,0],[1,1,0,0,1],[0,1,0,1,1]]
    输出:3
题眼:
思路:需要在判断岛屿个数的基础上,把相同形状的岛屿去重,也就是需要在遍历岛屿的基础上,增加hashset标记路径(相对岛屿起始点的路径:因为对矩阵的遍历总是
从左上到右下的,因此相同形状的岛屿起点总是位于左上角,添加的相对路径也能对应相等),最终hashset就是岛屿个数
'''


class Solution:
    def numberof_distinct_islands(self, grid: List[List[int]]) -> int:
        # 特殊情况
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        dq = deque()  # 用BFS做
        island = set()  # set自动去重
        visited = [[False]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                path = []
                if grid[i][j] == 1 and not visited[i][j]:  # 第一次被访问的岛屿
                    dq.append((i, j))  # 加入队列,并立刻标记
                    visited[i][j] = True
                    path.append(str((i-i, j-j)))  # 路径添加相对岛屿起点的坐标位置:用元组作为集合内元素也可以
                    while dq:  # BFS
                        x, y = dq.popleft()
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and grid[tx][ty] == 1 and not visited[tx][ty]:  # 有效、是岛屿、第一次访问
                                dq.append((tx, ty))  # 加入队列,并立刻标记
                                visited[tx][ty] = True
                                path.append(str((tx-i, ty-j)))  # 路径添加相对岛屿起点的坐标位置
                    island.add(''.join(path))
        return len(island)


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip()[1: -1].split(']')[: -1]
            grid = []
            for row in in_line:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            print(obj.numberof_distinct_islands(grid))
        except EOFError:
            break

804 · 不同岛屿的个数II

需要在判断岛屿个数的基础上,把相同形状(加上旋转和翻转,一共8种)的岛屿去重;对于每个形状的岛屿,都把原始位置添加上,经过正则函数返回该岛屿形状对应的唯一标识 ,正则函数逻辑:添加该形状的8种旋转、翻转变化,然后对每种形状各自排序后取最小值(排序这个逻辑不是很理解)

python 复制代码
from typing import List, Optional, Union
from collections import deque
'''
lintcode
804 · 不同岛屿的个数II
给定一个由0和1组成的非空的二维网格,一个岛屿是指四个方向(包括横向和纵向)都相连的一组1(1表示陆地)。你可以假设网格的四个边缘都被水包围。
计算不同岛屿的数量。当一个岛被认为与另一个岛相同时,它们有相同的形状,或在旋转后的形状相同(90,180,或270度)或翻转(左/右方向或向上/向下方向)。
示例 1:
    输入:
    [[1,1,0,0,0],[1,0,0,0,0],[0,0,0,0,1],[0,0,0,1,1]]
    输出:1
    解释:一共有两个岛,但是两个岛旋转后的形状相同
题眼:
思路:需要在判断岛屿个数的基础上,把相同形状(加上旋转和翻转,一共8种)的岛屿去重;对于每个形状的岛屿,都把原始位置添加上,经过正则函数返回该岛屿形状
对应的唯一标识,正则函数逻辑:添加该形状的8种旋转、翻转变化,然后对每种形状各自排序后取最小值(排序这个逻辑不是很理解)
'''


class Solution:
    def num_distinct_islands2(self, grid: List[List[int]]) -> int:
        # 特殊情况
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        dq = deque()  # 用BFS做
        island = set()  # set自动去重
        visited = [[False]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                shape = []
                if grid[i][j] == 1 and not visited[i][j]:  # 第一次被访问的岛屿
                    dq.append((i, j))  # 加入队列,并立刻标记
                    visited[i][j] = True
                    shape.append((i, j))  # 添加岛屿的第一个位置
                    while dq:  # BFS
                        x, y = dq.popleft()
                        for tx, ty in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
                            if 0 <= tx < m and 0 <= ty < n and grid[tx][ty] == 1 and not visited[tx][ty]:  # 有效、是岛屿、第一次访问
                                dq.append((tx, ty))  # 加入队列,并立刻标记
                                visited[tx][ty] = True
                                shape.append((tx, ty))  # 添加岛屿的其他位置
                    island.add(self.canonical(shape))  # 添加正则化后的岛屿
        return len(island)

    def canonical(self, shape):  # 这一部分老是写不对

        def encode(shape):
            x, y = shape[0]
            return "".join(str(i - x) + ':' + str(j - y) for i, j in shape)  # 减去起始点取相对位置是对排序后的形状进行的

        shapes = [[(a * i, b * j) for i, j in shape] for a, b in ((1, 1), (1, -1), (-1, 1), (-1, -1))]  # 添加 翻转
        shapes += [[(j, i) for i, j in shape] for shape in shapes]  # 添加 旋转

        return min(encode(sorted(shape)) for shape in shapes)  # 返回排序后的结果


if __name__ == "__main__":
    obj = Solution()
    while True:
        try:
            in_line = input().strip()[1: -1].split(']')[: -1]
            grid = []
            for row in in_line:
                grid.append([int(i) for i in row.split('[')[1].split(',')])
            print(obj.num_distinct_islands2(grid))
        except EOFError:
            break
相关推荐
simple_ssn21 分钟前
【C语言刷力扣】1502.判断能否形成等差数列
c语言·算法·leetcode
Curry_Math34 分钟前
LeetCode 热题100之技巧关卡
算法·leetcode
这个男人是小帅3 小时前
【GAT】 代码详解 (1) 运行方法【pytorch】可运行版本
人工智能·pytorch·python·深度学习·分类
DdddJMs__13510 小时前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
Sunyanhui111 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农11 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
AnFany16 小时前
LeetCode【0051】N皇后
python·算法·leetcode·回溯法·n皇后
可别是个可爱鬼16 小时前
代码随想录 -- 动态规划 -- 完全平方数
数据结构·python·算法·leetcode·动态规划
一直学习永不止步16 小时前
LeetCode题练习与总结:至少有 K 个重复字符的最长子串--395
java·算法·leetcode·字符串·滑动窗口·哈希表·分治
wx2004110217 小时前
树形dp总结
算法·深度优先·图论