LeetCode 130, 200, 694, 695, 733 (“岛” 系列): DFS方法题解(python)

本文详细介绍了使用DFS解决LeetCode中5个关于'岛'的问题,包括200题《Number of Islands》、130题《Surrounded Region》、694题《Number of Distinct Islands》、695题《Max Area of Island》和733题《Flood Fill》。通过DFS方法寻找岛屿的数量、形状、最大面积等,同时讨论了边界处理和旋转对称岛屿的判断。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode 130, 200, 694, 695, 733 (“岛” 系列): DFS方法题解(python)

描述:

Leetcode之中有一类题目,给定一个用二维数组表示的类似的迷宫/围棋/岛屿, 然后有诸如找有多少个岛屿(130, 200),或者有多少个不一样的岛屿(694),或者最大岛屿的面积(695)之类的要求。这些题目大体上存在三种解法: DFS(深度优先搜索),BFS(广度优先搜索),Union Find。在这里总结一下岛屿类问题的DFS方法求解。

链接: 130: Surrounded Region.
链接: 200: Number of Islands.
链接: 694: Number of Distinct Islands.
链接: 695: Max Area of Island.
链接: 733: Flood Fill.

200: Number of Islands.
问题描述:

给了一个二维数组,指定‘0’作为海水,‘1’作为陆地。问这个数组里有多少个岛屿。要求边境都是海水,并且连着的‘1’是作为一个岛屿。

Input: grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
Output: 1

Input: grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
Output: 3
思路:

这里使用dfs,找到所有由‘1’组成的路径。然后有多少个路径就有多少个岛屿。这是十分基础的DFS用法。

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid:
            return 0
        res = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    self.dfs(grid, i, j)
                    res+= 1
        return res
    def dfs(self, grid, i, j):  
        if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or grid[i][j] =='0':
            return  函数退出的条件
        grid[i][j] = '0'   函数的操作(函数的目的)
        self.dfs(grid, i, j-1)  递归
        self.dfs(grid, i, j+1)  递归
        self.dfs(grid, i-1, j)  递归
        self.dfs(grid, i+1, j)  递归
总结:

这道题dfs函数的写法几乎可以认为是这一类题目的dfs写法:先判断退出->函数操作(目的)-> 然后向四个方向递归即可。

130: Surrounded Region.
问题描述:

给定一个类似于围棋的棋盘(X, O为棋子),然后让把所有被X包围的O全部反转过来。其中在棋盘边界的O不需要翻转,被认为没有被X所包围。

思路:

这个题与岛屿数量那道题十分类似。不同的是,在边界上的‘O’,即“岛屿”不能被计算为岛屿,即不被翻转。

利用DFS,找到O相连的路径。看这个路径是不是全部被X所包围。如果是的话,把他们全部标记成另一个标记,如#,然后全局搜索,把所有的#都替换成X即可。注意,如果这个路径中有任何一个O在边界,那么这些O就都是“活的”,即无需替换。

这里先看代码,然后总结具体操作。
代码如下:

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        if not board:
            return
        for i in range(len(board)):
            if i==0 or i == len(board)-1:       遍历到第一行与最后一行
                for j in range(len(board[0])):
                    self.dfs(board, i, j)
            else:
                self.dfs(board, i, 0)
                self.dfs(board, i, len(board[0])-1)
        for i in range(len(board)):
            for j in range(len(board[0])):
                board[i][j] = 'X' if board[i][j] !='#' else 'O'
        
    def dfs(self, board, i, j):
        if i<0 or j<0 or i>=len(board) or j>=len(board[0]) or board[i][j] !='O':
            return
        board[i][j] = '#'
        self.dfs(board, i, j-1)
        self.dfs(board, i, j+1)
        self.dfs(board, i-1, j)
        self.dfs(board, i+1, j)
总结:

这道题和岛屿题的区别在于边界问题的处理。具体的做法是在遍历的时候,如果遍历到底一行或者最后一行,那么我们需要对所有的列做DFS,找到’O’的路径;如果遍历到的是中间行,那么只对在首列和尾列元素做DFS。这样就避免“误伤”位于边界上的“O”。

694: Number of Distinct Islands.
问题描述:

给了一个岛屿图,问这里头有多少不同的岛。注意旋转对称的两个岛屿要算成不同的岛屿。

11000
11000
00011
00011

Output: 1

11011
10000
00001
11011

Output: 3
思路:

大体思路还是在于用dfs把岛的路径给找出来。但是如何判断两个岛屿是否相同呢?由于旋转对称的岛屿不算是相同的岛屿,那么在做DFS找到岛屿路径的时候,相同的岛屿必定拥有相同的DFS搜索路径。把每个岛屿的路径给记录下来,看最后有多少个不同的路径,即对应着多少个不同的岛屿。

class Solution:
    def numDistinctIslands(self, grid: List[List[int]]) -> int:
        self.seen = set()
        res = set()

        for i in range(len(grid)):
            for j in range(len(grid[0])):
                self.path = []
                self.dfs(grid, i, j, 0)
                if self.path:
                    res.add(tuple(self.path))
        return len(res)
    def dfs(self, grid, i, j, direction):
        if i<0 or j<0 or i>=len(grid) or j>= len(grid[0]) or grid[i][j] ==0 or (i, j) in self.seen:    退出条件
            return
        self.seen.add((i, j))
        self.path.append(direction)   函数操作(目的)

        self.dfs(grid, i, j-1, 1)   递归向南:direction = 1
        self.dfs(grid, i, j+1, 2)   递归向北:direction = 2
        self.dfs(grid, i-1, j, 3)   递归向西:direction = 3
        self.dfs(grid, i+1, j, 4)   递归向东:direction = 4
        self.path.append(0)         解释见下方
总结:

大体上没有什么新东西,但是最后一句self.path.append(0),这里这句话的意思是为了标定退出时候的记号。就是因为这一句的存在,可以帮助判断旋转对称的两岛屿之间路径的问题。

695: Max Area of Island.
描述:

依旧是给了个岛屿。这次问这群岛屿中,面积最大的那个有多大。

[[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]]
Output: 6
思路:

这题相比694来说更为简单。用DFS,每次路径扩展一次,计数+1即可。然后找所有的计数里最大的那个就是答案。

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        self.seen = set()
        if not grid:
            return 0
        res = []
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                self.num = 0
                self.dfs(grid, i, j)
                if self.num:
                    res.append(self.num)
        if not res:
            return 0
        return max(res)
        
    def dfs(self, grid, i, j):
        if i<0 or j<0 or i>=len(grid) or j>=len(grid[0]) or (i, j) in self.seen or grid[i][j] ==0:
            return
        self.seen.add((i, j))
        self.num+=1
        self.dfs(grid, i-1, j)
        self.dfs(grid, i+1, j)
        self.dfs(grid, i, j-1)
        self.dfs(grid, i, j+1)
总结:

还是强化这类问题dfs函数的那三要素:退出条件,函数目的,递归方法

733: Flood Fill
问题描述:

给了一个图片,或者说一个调色盘,由二维数组表示。问选定这个调色盘中的某个点,然后给他赋予一个新的颜色,让所有和这个点连接的一样颜色的点全部都翻转成给定的新颜色。

Input: 
image = 
[[1,1,1],
 [1,1,0],
 [1,0,1]]
sr = 1, sc = 1, newColor = 2
Output: 
[[2,2,2],
 [2,2,0],
 [2,0,1]]
Explanation: 
From the center of the image (with position (sr, sc) = (1, 1)), all pixels connected 
by a path of the same color as the starting pixel are colored with the new color.
Note the bottom corner is not colored 2, because it is not 4-directionally connected
to the starting pixel.
思路:

这个题比岛屿问题还要简单,连遍历的步骤都省去了。找到那个需要变色的位置,然后做一个dfs即可。需要注意的可能是全局变量和局部变量之间的协调,这个根据实际情况调整即可。

class Solution:
    def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]:
        if not image:
            return
        self.color = image[sr][sc]
        if self.color == newColor:  如果给定的颜色和原来的颜色相同,直接return即可
            return image
        self.res = image
        self.dfs(self.res, sr, sc, newColor)
        return self.res
        
    def dfs(self, image, i, j, newColor):
        if i<0 or j<0 or i>=len(image) or j>=len(image[0]) or image[i][j] != self.color:
            return
        image[i][j] = newColor
        
        self.dfs(image, i-1, j, newColor)
        self.dfs(image, i+1, j, newColor)
        self.dfs(image, i, j-1, newColor)
        self.dfs(image, i, j+1, newColor)
总结:

这个题dfs方法与前面完全一样。从这个题本身来说,麻烦的地方最多也就是全局变量/局部遍历的选择而已。

总结:

类似的矩阵/岛屿/棋盘/迷宫问题还有很多,而且DFS不是唯一解,甚至有时候并不是最优解。比如200题,很多人会提出BFS 或者 union find的解法。此处仅总结DFS的用法,并试图在多个提醒中找到一个通用的“模版”来帮助强化记忆,进行总结。DFS还有几大应用:包括在树/图等数据结构中的应用,以及具体回溯(Backtracking)的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值