LeetCode 1091. 二进制矩阵中的最短路径
本节目标
用八方向网格 BFS 求从左上角到右下角经过格子的最少数量。
这道题是网格与状态图搜索中的最短路版本:开放格是状态,八个方向是邻接关系,路径长度按经过的格子数计。
朴素思路与瓶颈
枚举从左上到右下的所有简单路径会产生指数级分支,并且必须比较所有候选长度。由于每次移动只跨一条等权边,BFS 天然按路径长度递增;第一次取到终点时就是最短答案。
八方向 BFS 不变量
起点或终点被阻塞时没有路径,先返回 -1。否则从 (0, 0, 1) 入队,1 表示起点本身已计入长度。每次扩展八个方向,越界、障碍和已访问格都跳过。
本实现把入队的开放格改为非零,作为访问标记。标记发生在入队前,所以没有格子会由不同方向重复进入队列。单格开放矩阵的起点同时是终点,返回 1。
原地修改
搜索会把已经入队的 0 改为 1。这不会改变最短路径结论,却让网格同时承担障碍和访问集合的职责;若调用者必须保留原矩阵,可改用同尺寸 visited 矩阵。
代码实现
C++17 与 Python 3 均通过 shortestPathBinaryMatrix 接收网格,不读取输入流。队列保存坐标及当前路径长度,源码中的访问标记严格发生在入队阶段。
- C++
- Python
C++17
#include <array>
#include <queue>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
int size = static_cast<int>(grid.size());
if (grid[0][0] != 0 || grid[size - 1][size - 1] != 0) {
return -1;
}
const array<pair<int, int>, 8> directions{{
{-1, -1}, {-1, 0}, {-1, 1}, {0, -1},
{0, 1}, {1, -1}, {1, 0}, {1, 1},
}};
queue<array<int, 3>> states;
states.push({0, 0, 1});
grid[0][0] = 1;
while (!states.empty()) {
array<int, 3> current = states.front();
states.pop();
int row = current[0];
int col = current[1];
int distance = current[2];
if (row == size - 1 && col == size - 1) {
return distance;
}
for (const auto& [rowChange, colChange] : directions) {
int nextRow = row + rowChange;
int nextCol = col + colChange;
if (nextRow < 0 || nextRow >= size || nextCol < 0 || nextCol >= size) {
continue;
}
if (grid[nextRow][nextCol] != 0) {
continue;
}
// 网格改为非零即访问,随后才入队。
grid[nextRow][nextCol] = 1;
states.push({nextRow, nextCol, distance + 1});
}
}
return -1;
}
};
Python 3
from collections import deque
class Solution:
def shortestPathBinaryMatrix(self, grid: list[list[int]]) -> int:
size = len(grid)
if grid[0][0] != 0 or grid[size - 1][size - 1] != 0:
return -1
directions = [
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
]
states = deque([(0, 0, 1)])
grid[0][0] = 1
while states:
row, col, distance = states.popleft()
if row == size - 1 and col == size - 1:
return distance
for row_change, col_change in directions:
next_row = row + row_change
next_col = col + col_change
if (
next_row < 0
or next_row >= size
or next_col < 0
or next_col >= size
):
continue
if grid[next_row][next_col] != 0:
continue
# 网格改为非零即访问,随后才入队。
grid[next_row][next_col] = 1
states.append((next_row, next_col, distance + 1))
return -1
复杂度分析
设矩阵边长为 n。每个格子至多入队一次,八个方向为常数,时间复杂度为 O(n²);队列最坏保存 O(n²) 个格子,空间复杂度为 O(n²)。
易错点
- 把路径长度初始化为
0,使单格开放矩阵得到错误答案。 - 漏掉四个对角方向,无法通过只能斜向连通的矩阵。
- 起点或终点为
1时仍然开始 BFS。 - 直到出队才标记,导致相邻格在同层重复入队。
模式迁移
迷宫最少步数、最近出口和带障碍的棋盘移动都可复用此模型。变化通常只在方向集合、起终点定义和距离是否从格子数或边数起算;BFS 的层序与入队标记规则保持不变。