LeetCode 1162.地图分析

文章目录

题目描述

你现在手里有一份大小为 n x n 的网格 grid,上面的每个单元格都用 01 标记好了。其中 0 代表海洋,1 代表陆地。

请你找出一个海洋单元格 ,这个海洋单元格到离它最近的陆地单元格 的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1

我们这里说的距离是「曼哈顿距离」(Manhattan Distance):(x0, y0)(x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1|

示例 1

复制代码
输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。

示例 2

复制代码
输入:grid = [[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释:海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。

解题思路

为什么使用 BFS?

这道题的关键在于理解"最近 "和"最远"的关系:

  • 对于每个海洋单元格,我们需要找到它到最近陆地的距离
  • 然后在所有海洋中,找到这个距离最大的那个

如果用暴力方法:

  1. 对每个海洋单元格,计算到所有陆地的距离
  2. 取最小值作为该海洋的"最近距离"
  3. 最后在所有海洋中找最大值

时间复杂度会达到 O(n⁴),效率极低!

多源 BFS 的巧妙之处

转换思维 :与其从每个海洋找最近陆地,不如从所有陆地同时向外扩散

想象一个场景:

  • 所有陆地同时开始"淹没"周围的海洋
  • 每一轮扩散,距离增加 1
  • 最后被淹没的海洋,就是距离所有陆地都最远的那个!

这就是多源 BFS(Multi-Source BFS)的核心思想。


Python 代码实现

python 复制代码
from typing import List
from collections import deque


class Solution:
    def maxDistance(self, grid: List[List[int]]) -> int:
        """
        使用多源 BFS 找到距离陆地最远的海洋单元格

        思路:
        1. 将所有陆地单元格作为起点,同时开始 BFS
        2. 每一层 BFS 代表距离增加 1
        3. 最后一层到达的海洋单元格就是距离最远的

        Args:
            grid: n x n 的二维数组,0 代表海洋,1 代表陆地

        Returns:
            最远距离,如果只有陆地或海洋则返回 -1
        """
        n = len(grid)
        m = len(grid[0])

        # 初始化队列,将所有陆地单元格加入队列
        queue = deque()
        visited = [[False] * m for _ in range(n)]
        seas = 0  # 海洋单元格数量

        # 遍历网格,统计海洋数量并将陆地加入队列
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 1:
                    visited[i][j] = True
                    queue.append((i, j))
                else:
                    visited[i][j] = False
                    seas += 1

        # 如果全是陆地或全是海洋,返回 -1
        if seas == 0 or seas == n * m:
            return -1

        # 方向数组:上、右、下、左
        moves = [-1, 0, 1, 0, -1]

        level = 0  # 记录 BFS 的层数,即距离

        # BFS 遍历
        while queue:
            level += 1
            size = len(queue)

            # 处理当前层的所有节点
            for _ in range(size):
                x, y = queue.popleft()

                # 向四个方向扩展
                for i in range(4):
                    nx = x + moves[i]
                    ny = y + moves[i + 1]

                    # 检查边界和是否访问过
                    if 0 <= nx < n and 0 <= ny < m and not visited[nx][ny]:
                        visited[nx][ny] = True
                        queue.append((nx, ny))

        # 返回 level - 1,因为最后一层是无效的(没有新的海洋单元格)
        return level - 1

代码详细解析

初始化阶段

python 复制代码
n = len(grid)
m = len(grid[0])

queue = deque()
visited = [[False] * m for _ in range(n)]
seas = 0  # 海洋单元格数量

for i in range(n):
    for j in range(m):
        if grid[i][j] == 1:  # 陆地
            visited[i][j] = True      # 标记为已访问
            queue.append((i, j))      # 加入队列作为 BFS 起点
        else:  # 海洋
            visited[i][j] = False
            seas += 1

关键操作

  • 将所有陆地单元格加入队列(多源起点)
  • 陆地标记为 visited=True(避免重复访问)
  • 统计海洋数量 seas(用于边界判断)

边界判断

python 复制代码
if seas == 0 or seas == n * m:
    return -1

两种特殊情况

  • seas == 0:全是陆地,没有海洋 → 返回 -1
  • seas == n * m:全是海洋,没有陆地 → 返回 -1

方向数组技巧

python 复制代码
moves = [-1, 0, 1, 0, -1]

这个设计非常巧妙!通过相邻两个元素的组合表示四个方向:

复制代码
i=0: moves[0]=-1, moves[1]=0   → 向上 (x-1, y+0)
i=1: moves[1]=0,  moves[2]=1   → 向右 (x+0, y+1)
i=2: moves[2]=1,  moves[3]=0   → 向下 (x+1, y+0)
i=3: moves[3]=0,  moves[4]=-1  → 向左 (x+0, y-1)

相比写四个元组 [(−1,0), (0,1), (1,0), (0,−1)],这种方式更简洁!

BFS 核心循环

python 复制代码
level = 0  # 记录 BFS 的层数,即距离

while queue:
    level += 1
    size = len(queue)

    # 处理当前层的所有节点
    for _ in range(size):
        x, y = queue.popleft()

        # 向四个方向扩展
        for i in range(4):
            nx = x + moves[i]
            ny = y + moves[i + 1]

            # 检查边界和是否访问过
            if 0 <= nx < n and 0 <= ny < m and not visited[nx][ny]:
                visited[nx][ny] = True
                queue.append((nx, ny))

return level - 1

关键点解析

代码 作用
level += 1 每处理一层,距离 +1
size = len(queue) 记录当前层的节点数(层序遍历关键)
for _ in range(size) 只处理当前层的节点,保证层次分明
queue.popleft() 取出队首节点(双端队列 O(1) 复杂度)
visited[nx][ny] = True 立即标记,避免重复入队
queue.append((nx, ny)) 新节点加入队列尾部

为什么返回 level - 1

最后一层 BFS 时,队列中的节点已经没有未访问的邻居了,所以 level 会多加 1,需要减去。


示例执行流程

示例 1:grid = [[1,0,1],[0,0,0],[1,0,1]]

初始状态
复制代码
网格:        visited:       queue:
1 0 1        T F T          [(0,0), (0,2), (2,0), (2,2)]
0 0 0   →    F F F   →      seas = 5
1 0 1        T F T
BFS 第 1 层(level=1)
复制代码
从 4 个陆地扩散:
- (0,0) → 访问 (0,1), (1,0)
- (0,2) → 访问 (1,2)
- (2,0) → 访问 (2,1)
- (2,2) → 无新节点

结果:
T T T
T F T
T T T

queue = [(0,1), (1,0), (1,2), (2,1)]
level = 1
BFS 第 2 层(level=2)
复制代码
从 4 个海洋节点扩散:
- (0,1) → 访问 (1,1)  中心海洋
- (1,0) → 无新节点
- (1,2) → 无新节点
- (2,1) → 无新节点

结果:
T T T
T T T  ← 全部访问!
T T T

queue = [(1,1)]
level = 2
BFS 第 3 层(level=3)
复制代码
处理最后一个节点 (1,1):
- 四个方向都已访问,无新节点

queue = []  ← 空队列
level = 3
返回结果
复制代码
return level - 1 = 3 - 1 = 2  

示例 2:grid = [[1,0,0],[0,0,0],[0,0,0]]

执行过程概览
复制代码
初始:
1 0 0  →  queue = [(0,0)]
0 0 0
0 0 0

level=1: 从 (0,0) 扩散
1 1 0  →  queue = [(0,1), (1,0)]
1 0 0
0 0 0

level=2: 扩散
1 1 1  →  queue = [(1,1), (0,2), (2,0)]
1 1 0
1 0 0

level=3: 扩散
1 1 1  →  queue = [(1,2), (2,1)]
1 1 1
1 1 0

level=4: 扩散
1 1 1  →  queue = [(2,2)]  最后一个海洋
1 1 1
1 1 1

level=5: 处理 (2,2),无新节点
queue = []  ← 空!

返回:level - 1 = 5 - 1 = 4  

复杂度分析

类型 复杂度 说明
时间复杂度 O(n²) 每个单元格最多被访问一次
空间复杂度 O(n²) visited 数组 + 队列空间

算法精髓总结

多源 BFS vs 单源 BFS

特性 单源 BFS 多源 BFS
起点数量 1 个 多个
适用场景 最短路径 最近距离场
初始化 加入 1 个起点 加入所有起点

关键技巧

  1. 多源起点:将所有陆地同时作为 BFS 起点
  2. 层序遍历 :用 size 控制每层处理的节点数
  3. 方向数组moves = [-1, 0, 1, 0, -1] 的巧妙设计
  4. 立即标记 :访问后立即标记 visited=True,避免重复入队
  5. 边界处理:全陆地/全海洋的特殊情况判断

为什么这个解法优雅?

效率高 :O(n²) 时间复杂度,每个单元格只访问一次
代码简洁 :方向数组 + 层序遍历,代码量少
思路巧妙 :逆向思维,从陆地扩散而非海洋搜索
通用性强:可推广到类似的"最近距离场"问题



相关推荐
leaves falling2 小时前
搜索插入位置(第一个≥target的位置)
算法
寒月小酒2 小时前
3.20 OJ
算法
AI科技星2 小时前
基于空间光速螺旋归一化的动力学方程推导与数值验证
人工智能·线性代数·算法·机器学习·平面
bbbb3652 小时前
排序算法的演进史:从冒泡到快速再到TimSort的技术7
数据结构·算法·排序算法
不染尘.2 小时前
排序算法详解1
开发语言·数据结构·c++·算法·排序算法
Tisfy2 小时前
LeetCode 3567.子矩阵的最小绝对差:暴力模拟
leetcode·矩阵·题解·模拟·暴力
啊我不会诶2 小时前
25CCPC东北邀请赛vp补题
c++·算法
plus4s2 小时前
3月20日(进阶11)
c++·算法