You are given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1.
A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that:
All the visited cells of the path are 0.
All the adjacent cells of the path are 8-directionally connected (i.e., they are different and they share an edge or a corner).
The length of a clear path is the number of visited cells of this path.
Example 1:
Input: grid = [
[0,1,0],
[1,0,0],
[1,1,0]
]
Output: 3Example 2:
Input: grid = [
[1,0],
[1,1]
]
Output: -1Constraints:
1 <= grid.length == grid[i].length <= 100grid[i][j] is 0 or 1.Before attempting this problem, you should be comfortable with:
We need to find the shortest path from the top-left corner to the bottom-right corner in a binary matrix, where we can only travel through cells containing 0. Since we want the shortest path and each step has equal weight, BFS is the natural choice. BFS explores cells level by level, guaranteeing that the first time we reach the destination, we have found the shortest path. We can move in all 8 directions (horizontal, vertical, and diagonal), so we check all 8 neighbors at each step.
(0, 0) or end cell (N-1, N-1) is blocked (contains 1), return -1.(0, 0, 1) where 1 represents the path length.(r, c, length).(N-1, N-1), return length as the shortest path.0.length + 1.-1 (no path exists).class Solution:
def shortestPathBinaryMatrix(self, grid: list[list[int]]) -> int:
N = len(grid)
if grid[0][0] or grid[N - 1][N - 1]:
return -1
q = deque([(0, 0, 1)])
visit = set((0, 0))
direct = [(0, 1), (1, 0), (0, -1), (-1, 0),
(1, 1), (-1, -1), (1, -1), (-1, 1)]
while q:
r, c, length = q.popleft()
if r == N - 1 and c == N - 1:
return length
for dr, dc in direct:
nr, nc = r + dr, c + dc
if (0 <= nr < N and 0 <= nc < N and grid[nr][nc] == 0 and
(nr, nc) not in visit):
q.append((nr, nc, length + 1))
visit.add((nr, nc))
return -1This approach is similar to the standard BFS but optimizes space by using the input grid itself to store distances. Instead of maintaining a separate visited set, we overwrite each cell with its distance from the start. A cell value of 0 indicates unvisited, while any positive value represents the shortest distance to reach that cell. This eliminates the need for extra space while preserving the BFS guarantee of finding the shortest path.
-1.(0, 0) and set grid[0][0] = 1 (distance of 1).(r, c) and read its distance from grid[r][c].distance.0:dist + 1.-1 if the destination is unreachable.class Solution:
def shortestPathBinaryMatrix(self, grid: list[list[int]]) -> int:
N = len(grid)
direct = [0, 1, 0, -1, 0, 1, 1, -1, -1, 1]
if grid[0][0] or grid[N - 1][N - 1]:
return -1
q = deque([(0, 0)])
grid[0][0] = 1
while q:
r, c = q.popleft()
dist = grid[r][c]
if r == N - 1 and c == N - 1:
return dist
for d in range(9):
nr, nc = r + direct[d], c + direct[d + 1]
if 0 <= nr < N and 0 <= nc < N and grid[nr][nc] == 0:
grid[nr][nc] = dist + 1
q.append((nr, nc))
return -1Standard BFS explores outward from the source in all directions. Bidirectional BFS runs two searches simultaneously: one from the start and one from the end. When the two search frontiers meet, we have found the shortest path. This reduces the search space significantly because instead of exploring a circle of radius d, we explore two circles of radius d/2. The total area explored is roughly half of what single-direction BFS would cover.
-1. If the grid is 1x1, return 1.q1 starting from (0, 0) and q2 starting from (N-1, N-1).-1 and the end cell with -2 to distinguish the two frontiers.q1 and q2:end or start), return the current path length.0), mark it with the current frontier's marker and add it to the queue.-1 if the frontiers never meet.class Solution:
def shortestPathBinaryMatrix(self, grid: list[list[int]]) -> int:
N = len(grid)
if grid[0][0] or grid[N - 1][N - 1]:
return -1
if N == 1:
return 1
direct = [0, 1, 0, -1, 0, 1, 1, -1, -1, 1]
q1 = deque([(0, 0)])
q2 = deque([(N - 1, N - 1)])
grid[0][0] = -1
grid[N - 1][N - 1] = -2
res = 2
start, end = -1, -2
while q1 and q2:
for _ in range(len(q1)):
r, c = q1.popleft()
for d in range(9):
nr, nc = r + direct[d], c + direct[d + 1]
if 0 <= nr < N and 0 <= nc < N:
if grid[nr][nc] == end:
return res
if grid[nr][nc] == 0:
grid[nr][nc] = start
q1.append((nr, nc))
q1, q2 = q2, q1
start, end = end, start
res += 1
return -1Before starting BFS, verify that both the starting cell (0, 0) and the destination cell (n-1, n-1) are passable (value 0). If either is blocked (value 1), return -1 immediately. Missing this check causes BFS to run unnecessarily or return incorrect results.
This problem allows diagonal movement, so all 8 directions must be explored: up, down, left, right, and the four diagonals. Using only the standard 4-directional movement leads to longer paths or failure to find a path that exists through diagonal moves.
Cells must be marked as visited when they are added to the queue, not when they are dequeued. Marking upon dequeue allows the same cell to be added multiple times from different neighbors, causing incorrect path lengths and memory issues. Always mark visited immediately upon enqueueing.