问题描述
小R正在处理一个 m x n 大小的二进制矩阵 grid,其中 0 表示海洋单元格,1 表示陆地单元格。一个移动是指从一个陆地单元格走到相邻的另一个陆地单元格,或者跨过网格的边界。
小R想知道有多少陆地单元格无法通过任意次数的移动离开网格的边界。也就是说,找出所有被海洋或边界包围的陆地单元格。
测试样例
示例一:
输入:
grid = [[0, 0, 0, 0], [1, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]输出:
3
示例二:
输入:
grid = [[1, 1, 1, 1], [1, 0, 0, 1], [1, 0, 0, 1], [1, 1, 1, 1]]输出:
0
示例三:
输入:
grid = [[0, 0, 1, 0], [1, 0, 1, 0], [0, 0, 1, 0]]输出:
0
解题思路
-
问题建模:首先,我们需要对问题进行建模。在这个问题中,我们将二维矩阵中的陆地单元格视为图中的节点,海洋单元格视为图中的空白区域。我们的目标是找出所有无法通过任何路径到达边界的陆地单元格,这可以类比为图论中的连通分量问题。
-
DFS算法选择:深度优先搜索(DFS)算法是解决此类问题的理想选择,因为它可以有效地遍历图中的所有节点,同时使用栈(在递归实现中)来存储遍历路径,确保每个节点都被访问一次。
-
边界遍历策略:我们选择从矩阵的边界开始遍历,这是因为边界单元格是最有可能成为孤立部分的起点。通过从边界开始,我们可以确保所有可达的陆地单元格都被标记。
-
状态标记:为了区分已访问和未访问的陆地单元格,我们引入了一个额外的矩阵
visited。这个矩阵与输入矩阵grid大小相同,用于存储每个单元格的访问状态。 -
递归逻辑设计:在DFS递归中,我们需要设计逻辑来决定何时继续递归,何时回溯。在这个问题中,当我们遇到一个海洋单元格或已访问过的陆地单元格时,我们回溯;当我们遇到一个未访问的陆地单元格时,我们递归到它的邻居。
核心代码
def solution(grid: list) -> int:
# 如果网格为空或者第一行为空,表示没有陆地,返回0
if not grid or not grid[0]:
return 0
# 获取网格的行数和列数
m, n = len(grid), len(grid[0])
# 初始化一个同样大小的矩阵,用于记录访问过的陆地单元格
visited = [[False] * n for _ in range(m)]
# 定义四个移动方向:右、下、左、上
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
# 检查坐标(x, y)是否在网格内
def is_valid(x, y):
return 0 <= x < m and 0 <= y < n
# 深度优先搜索函数,用于标记从(x, y)可达的所有陆地单元格
def dfs(x, y):
# 如果坐标无效、已访问过或为海洋单元格,返回
if not is_valid(x, y) or visited[x][y] or grid[x][y] == 0:
return
# 标记当前单元格为已访问
visited[x][y] = True
# 遍历所有方向
for dx, dy in directions:
# 递归搜索相邻单元格
dfs(x + dx, y + 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) # 下边界
# 计数无法到达边界的陆地单元格
closed_count = 0
for i in range(m):
for j in range(n):
# 如果单元格为陆地且未访问过,计数加一
if grid[i][j] == 1 and not visited[i][j]:
closed_count += 1
# 返回无法到达边界的陆地单元格数量
return closed_count
if __name__ == '__main__':
print(solution(grid=[[0, 0, 0, 0]]) == 3)
print(solution(grid=[[1, 1, 1, 1], [1, 0, 0, 1], [1, 0, 0, 1], [1, 1, 1, 1]]) == 0)
print(solution(grid=[[0, 0, 1, 0], [1, 0, 1, 0], [0, 0, 1, 0]]) == 0)
核心知识
-
深度优先搜索(DFS) :DFS是一种图遍历算法,它从一个节点开始,尽可能深地搜索图的分支。在这个问题中,DFS用于遍历所有可达的陆地单元格,通过递归的方式,我们可以保证每个节点都被访问一次。
-
状态存储:在DFS中,状态存储是关键。在这个问题中,我们使用
visited矩阵来存储每个单元格的访问状态,这有助于避免重复访问和无限递归。 -
边界条件处理:在DFS中,边界条件的处理非常重要。在这个问题中,边界条件是触发DFS的起点,因为边界单元格是最容易成为孤立部分的单元格。
-
递归与回溯:递归是DFS的核心,它允许算法深入问题的每个可能解决方案。回溯是递归过程中的反向操作,当一个路径被完全探索后,算法会回溯到上一个决策点,尝试其他可能的路径。
-
图论概念应用:虽然这个问题是在一个二维矩阵上定义的,但它本质上是一个图论问题。陆地单元格可以被视为图的节点,而移动可以被视为图的边。通过图论的视角,我们可以更深入地理解问题和解决方案。
动态规划(DP)的应用
虽然这个问题主要通过DFS解决,但动态规划的概念在这里同样适用。我们可以将问题视为一个优化问题,其中我们需要最小化无法到达边界的陆地单元格数量。通过递归地决定每个单元格的状态(可达或不可达),我们实际上是在解决一个决策过程,这与动态规划的思想相吻合。
使用豆包marscode AI的优势
-
MarsCode AI能够根据算法题干描述快速生成代码框架,这大大节省了编写初始代码的时间。对于初学者或者在面对复杂问题时,这样的辅助可以快速搭建起解题的基础结构,让开发者能够将更多的精力投入到问题的理解和算法的设计上。
-
AI可以分析已有的代码,提供优化建议。这包括但不限于减少时间复杂度、优化空间使用、提升代码可读性等方面。通过AI的优化建议,开发者可以编写出更高效、更优雅的代码。
-
MarsCode AI具备错误检测能力,能够在代码编写过程中及时发现潜在的问题,并提供修改建议。这有助于减少调试时间,避免在错误追踪上浪费过多的精力,确保代码的正确性和稳定性。
-
对于初学者或者在遇到难以理解的算法时,MarsCode AI能够提供学习辅助。它能够解释代码中的复杂概念,帮助开发者理解如DFS(深度优先搜索)等高级算法。这种辅助学习的功能,可以加快学习过程,提高解题技能。