文章目录
-
- 题目描述
-
- [示例 1](#示例 1)
- [示例 2](#示例 2)
- 解题思路
-
- [为什么使用 BFS?](#为什么使用 BFS?)
- [多源 BFS 的巧妙之处](#多源 BFS 的巧妙之处)
- [Python 代码实现](#Python 代码实现)
- 代码详细解析
- 示例执行流程
-
- [示例 1:`grid = [[1,0,1],[0,0,0],[1,0,1]]`](#示例 1:
grid = [[1,0,1],[0,0,0],[1,0,1]]) - [示例 2:`grid = [[1,0,0],[0,0,0],[0,0,0]]`](#示例 2:
grid = [[1,0,0],[0,0,0],[0,0,0]])
- [示例 1:`grid = [[1,0,1],[0,0,0],[1,0,1]]`](#示例 1:
- 复杂度分析
- 算法精髓总结
-
- [多源 BFS vs 单源 BFS](#多源 BFS vs 单源 BFS)
- 关键技巧
- 为什么这个解法优雅?
题目描述
你现在手里有一份大小为 n x n 的网格 grid,上面的每个单元格都用 0 和 1 标记好了。其中 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?
这道题的关键在于理解"最近 "和"最远"的关系:
- 对于每个海洋单元格,我们需要找到它到最近陆地的距离
- 然后在所有海洋中,找到这个距离最大的那个
如果用暴力方法:
- 对每个海洋单元格,计算到所有陆地的距离
- 取最小值作为该海洋的"最近距离"
- 最后在所有海洋中找最大值
时间复杂度会达到 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:全是陆地,没有海洋 → 返回 -1seas == 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 个起点 | 加入所有起点 |
关键技巧
- 多源起点:将所有陆地同时作为 BFS 起点
- 层序遍历 :用
size控制每层处理的节点数 - 方向数组 :
moves = [-1, 0, 1, 0, -1]的巧妙设计 - 立即标记 :访问后立即标记
visited=True,避免重复入队 - 边界处理:全陆地/全海洋的特殊情况判断
为什么这个解法优雅?
效率高 :O(n²) 时间复杂度,每个单元格只访问一次
代码简洁 :方向数组 + 层序遍历,代码量少
思路巧妙 :逆向思维,从陆地扩散而非海洋搜索
通用性强:可推广到类似的"最近距离场"问题