二进制矩阵最短路径:八个方向,一层一步
0 可走、1 阻塞。每步可走八邻域。无权图的最短格子数就是 BFS 层数;第一次弹出右下角即答案。
起点或终点是 1 则 −1。从 (0,0) 入队,步数从 1 计(路径含起点)。按层扩展 8 个方向,走进 0 就原地置 1。弹出 (n−1,n−1) 时返回当前步数。队列空则 −1。时间 O(n²),空间 O(n²)。
这是 LeetCode 1091. Shortest Path in Binary Matrix。n×n 的网格,0 能走、1 不能走。从左上 (0,0) 走到右下 (n−1,n−1),每一步可以走到相邻 8 格(含对角)。返回路径上的格子数;走不到返回 −1。
主例是 3×3:四周一圈 0,正中 (1,1) 是 1。不能斜穿中心走 3 格。一条最短路是 (0,0)→(0,1)→(1,2)→(2,2),长度 4。演示按层标出步 1、步 2、步 3 的前沿,最后给出 4。
第一直觉是 DFS 搜所有路径取最短。网格一大约指数爆炸,还容易和「能走到」搞混。缺的是无权图里「先到的一定更短」。下面从这块缺口用 BFS 分层。
八邻域不改变「先到即最短」
路径长度按经过的格子数算,每走一格 +1,对角和上下左右代价相同。这是无权图。起点也算进长度,所以从 (0,0) 出发时步数是 1,不是 0。单独一格的 1×1 且值为 0,答案是 1。
缺的信息是「第一次到达终点用了几步」。DFS 先走一条绕远的,再走一条近的,必须全局比;还要防止在 0 的空地里转圈。BFS 按距起点的步数分层:第 k 层都是最少 k 步能到的格子。第一次从队列里拿出终点,k 就是最短。
从缺口写出 8 方向 BFS。起点或终点为 1,直接 −1。队列放 (0,0),把起点改成 1 当访问标记。每轮先记下本层 size,弹出一个格子,若是终点返回 steps;否则把八个邻居里仍为 0 的改成 1 并入队。一层处理完 steps+1。方向向量是 (±1,0)、(0,±1)、(±1,±1) 共八个。
用手走主例。步 1,前沿只有 (0,0)。它的八邻里合法且为 0 的是 (0,1) 和 (1,0);(1,1) 是障碍。步 2,前沿 [(0,1),(1,0)]。再扩一层,演示标出绕过中心后的 (1,2)、(2,0)、(2,1)。步 3 还没踩到终点。再走一步第一次到达 (2,2),返回 4。
访问必须入队时标记,不能弹出再标,否则同一格会进队多次。不要把长度理解成边数:题目要格子数,代码里 steps 从 1 起。四方向版本会变题。每格最多入队一次,时间 O(n²)。
对角不是捷径特权对角一步仍只 +1,所以有时比先横再竖短。主例中心被堵,对角 (0,0)→(1,1)→(2,2) 不合法,最短仍是 4,不是 3。
Go:8 方向 BFS
func shortestPathBinaryMatrix(grid [][]int) int {n := len(grid)if grid[0][0] != 0 || grid[n-1][n-1] != 0 { return -1 }q := [][2]int{{0, 0}}grid[0][0] = 1steps := 1dirs := [8][2]int{{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}}for len(q) > 0 {size := len(q)for k := 0; k < size; k++ {cur := q[0]; q = q[1:]if cur[0] == n-1 && cur[1] == n-1 { return steps }for _, d := range dirs {ni, nj := cur[0]+d[0], cur[1]+d[1]if ni >= 0 && ni < n && nj >= 0 && nj < n && grid[ni][nj] == 0 {grid[ni][nj] = 1q = append(q, [2]int{ni, nj})}}}steps++}return -1}
1首尾不是 0,没有路径。1×1 的 [0] 会在弹出起点时返回 1。
2grid[i][j]=1 表示来过。入队即标记,避免同层重复入队。
3size 切层。主例步 1 只处理 (0,0),步 2 处理 (0,1) 和 (1,0)。
4八个方向写全。弹出终点才返回 steps,主例是 4。
总结
8 向 BFS,层数即格子数。主例中心挡住斜穿,最短 4。
- 对角合法,但不等于可以踩 1。主例 (1,1) 进不去,所以不是 3。
- steps 从 1 计,因为长度含起点。
- 入队时置 1。队列空仍没弹出终点,返回 −1。