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

1、本次总结的题目通常是在二维矩阵考察连通域/岛屿问题,常用的方法包括深度优先搜索、广度优先搜索和并查集,根据具体的题目可以选择最合适的方法,我个人优选在逻辑思维上简单直观的广度优先搜索方法
2、二维矩阵考察连通域/岛屿问题,包括简单的连通域染色、岛屿数量、飞地数量、岛屿面积等,复杂一点的题目考察对每个连通域/岛屿如何更好地标记,比如最大人工岛(简单的数字标记)、岛屿形状(相对位置连起来的元组或字符串标记)等

733. 图像渲染

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

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

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、注意细节就是入队的位置及时标记,否则可能会超时

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

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. 飞地的数量”一样的题意,正常广度优先搜索方法基础上,额外添加 连通域是否为飞地的判断即可

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

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的判断即可

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

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

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 后的最大面积判断:需要额外添加 简单数字的岛屿标记矩阵及岛屿标记-面积哈希表

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就是岛屿个数

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种旋转、翻转变化,然后对每种形状各自排序后取最小值(排序这个逻辑不是很理解)

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

猜你喜欢

转载自blog.csdn.net/qq_39975984/article/details/132911879