542. 01 Matrix
Problem Description
You are given an m x n
binary matrix mat
containing only 0s and 1s. For each cell in the matrix, you need to find the distance to the nearest cell containing a 0.
The distance between two cells is calculated using the Manhattan distance - cells that share a common edge have a distance of 1. This means you can only move horizontally or vertically (not diagonally) from one cell to another, and each such move counts as 1 unit of distance.
For example, if you have a matrix:
1 0 1 1 1 1 0 1 1
The output would be:
1 0 1 2 1 2 0 1 2
Each cell containing a 0 has a distance of 0 to itself. For cells containing 1, the value represents the minimum number of steps needed to reach any cell containing a 0.
The solution uses a multi-source BFS approach. First, all cells containing 0 are added to a queue and marked with distance 0 in the answer matrix. Then, BFS expands outward from all these 0-cells simultaneously, layer by layer. When visiting a cell (i, j)
, it checks all four adjacent cells. If an adjacent cell hasn't been visited yet (marked as -1), it gets assigned a distance of ans[i][j] + 1
and is added to the queue for further expansion. This ensures that each cell gets the minimum distance to the nearest 0.
The dirs = (-1, 0, 1, 0, -1)
pattern with pairwise
is a compact way to generate the four directional movements: (-1, 0)
, (0, 1)
, (1, 0)
, (0, -1)
for up, right, down, and left respectively.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The matrix can be viewed as a graph where each cell is a node, and adjacent cells (up, down, left, right) are connected by edges. We need to find shortest distances from multiple source nodes (cells with 0) to all other nodes.
Is it a tree?
- No: The matrix graph has multiple paths between cells and can have cycles (you can move in a rectangle and come back to the starting position).
Is the problem related to directed acyclic graphs?
- No: The matrix represents an undirected graph (you can move both ways between adjacent cells), and it can contain cycles.
Is the problem related to shortest paths?
- Yes: We need to find the shortest distance from each cell to the nearest 0. This is a classic shortest path problem.
Is the graph Weighted?
- No: All edges have the same weight of 1 (moving from one cell to an adjacent cell always has a distance of 1).
BFS
- Yes: Since we have an unweighted graph and need to find shortest paths, BFS is the perfect choice. BFS explores nodes level by level, guaranteeing that when we first reach a cell, we've found the shortest path to it.
Conclusion: The flowchart suggests using BFS (Breadth-First Search) for finding the shortest distance from each cell to the nearest 0. The multi-source BFS approach is particularly efficient here, starting from all 0-cells simultaneously and expanding outward layer by layer.
Intuition
The key insight is to think of this problem in reverse. Instead of finding the distance from each 1
to the nearest 0
, we start from all 0
s and expand outward simultaneously.
Imagine dropping stones into water at multiple points - each 0
is like a stone creating ripples. These ripples expand outward at the same speed, and when a ripple reaches a cell for the first time, that's the shortest distance from that cell to any 0
.
Why does this work better than starting from each 1
? If we started from each 1
and searched for the nearest 0
, we'd potentially repeat a lot of work. For a matrix with many 1
s and few 0
s, we'd be running BFS many times.
By using multi-source BFS starting from all 0
s simultaneously, we:
- Visit each cell exactly once
- Guarantee that the first time we reach a cell, we've found the shortest path to it
- Achieve optimal time complexity of
O(m × n)
The BFS naturally processes cells in layers:
- Layer 0: All cells containing
0
(distance = 0) - Layer 1: All cells adjacent to any
0
(distance = 1) - Layer 2: All cells at distance 2 from the nearest
0
- And so on...
This layer-by-layer expansion ensures that when we first visit a cell, we've found its minimum distance to any 0
. We mark visited cells (using ans[x][y] = -1
as "unvisited") to avoid processing them again, which prevents infinite loops and ensures correctness.
Learn more about Breadth-First Search and Dynamic Programming patterns.
Solution Approach
The implementation follows the multi-source BFS pattern we identified:
1. Initialize the answer matrix and queue:
- Create an
ans
matrix of the same size asmat
, initialized with-1
to mark all cells as unvisited - Create a queue
q
usingdeque()
for efficient BFS operations - Traverse the input matrix to find all
0
s, set their distance inans
to0
, and add their coordinates to the queue
ans = [[-1] * n for _ in range(m)]
q = deque()
for i, row in enumerate(mat):
for j, x in enumerate(row):
if x == 0:
ans[i][j] = 0
q.append((i, j))
2. Define directional movements: The code uses a compact pattern to represent four directions:
dirs = (-1, 0, 1, 0, -1)
When paired using pairwise(dirs)
, this generates: (-1, 0)
for up, (0, 1)
for right, (1, 0)
for down, and (0, -1)
for left.
3. Perform BFS expansion:
while q: i, j = q.popleft() # Get current cell for a, b in pairwise(dirs): # Check all 4 directions x, y = i + a, j + b # Calculate neighbor coordinates if 0 <= x < m and 0 <= y < n and ans[x][y] == -1: ans[x][y] = ans[i][j] + 1 # Set distance q.append((x, y)) # Add to queue for further expansion
The BFS process:
- Dequeue a cell
(i, j)
from the front of the queue - Explore its four adjacent cells
- For each valid, unvisited neighbor
(x, y)
:- Mark it as visited by setting
ans[x][y] = ans[i][j] + 1
- Add it to the queue to explore its neighbors in the next layer
- Mark it as visited by setting
4. Return the result:
Once the queue is empty, all reachable cells have been assigned their minimum distances, and we return ans
.
The time complexity is O(m × n)
since each cell is visited at most once, and the space complexity is O(m × n)
for the answer matrix and the queue (in the worst case, all cells are 0
s).
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the multi-source BFS approach:
Input matrix:
1 1 0 1 1 1 0 1 1
Step 1: Initialize
- Create
ans
matrix filled with-1
(unvisited):
-1 -1 -1 -1 -1 -1 -1 -1 -1
- Find all
0
s, mark them inans
with distance0
, and add to queue:- Found
0
at (0,2): setans[0][2] = 0
, queue = [(0,2)] - Found
0
at (2,0): setans[2][0] = 0
, queue = [(0,2), (2,0)]
- Found
-1 -1 0 -1 -1 -1 0 -1 -1
Step 2: BFS Layer 1 (distance = 1)
- Process (0,2): Check neighbors
- Up: out of bounds
- Right: out of bounds
- Down (1,2): unvisited, set
ans[1][2] = 1
, add to queue - Left (0,1): unvisited, set
ans[0][1] = 1
, add to queue
- Process (2,0): Check neighbors
- Up (1,0): unvisited, set
ans[1][0] = 1
, add to queue - Right (2,1): unvisited, set
ans[2][1] = 1
, add to queue - Down: out of bounds
- Left: out of bounds
- Up (1,0): unvisited, set
After Layer 1:
-1 1 0 1 -1 1 0 1 -1
Queue = [(1,2), (0,1), (1,0), (2,1)]
Step 3: BFS Layer 2 (distance = 2)
- Process (1,2): Check neighbors
- Neighbor (1,1) is unvisited, set
ans[1][1] = 2
, add to queue - Other neighbors already visited or out of bounds
- Neighbor (1,1) is unvisited, set
- Process (0,1): Check neighbors
- Neighbor (0,0) is unvisited, set
ans[0][0] = 2
, add to queue - Other neighbors already visited
- Neighbor (0,0) is unvisited, set
- Process (1,0): Check neighbors
- Neighbor (1,1) already set to 2 (reached from another path)
- Other neighbors already visited
- Process (2,1): Check neighbors
- Neighbor (2,2) is unvisited, set
ans[2][2] = 2
, add to queue - Other neighbors already visited
- Neighbor (2,2) is unvisited, set
After Layer 2:
2 1 0 1 2 1 0 1 2
Queue = [(1,1), (0,0), (2,2)]
Step 4: BFS Layer 3
- Process remaining cells in queue, but all their neighbors are already visited
- Queue becomes empty
Final Result:
2 1 0 1 2 1 0 1 2
Each cell now contains the minimum Manhattan distance to the nearest 0
. Notice how the BFS expanded simultaneously from both 0
s, and cells were assigned distances based on which source reached them first.
Solution Implementation
1from collections import deque
2from typing import List
3
4class Solution:
5 def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
6 # Get matrix dimensions
7 rows, cols = len(mat), len(mat[0])
8
9 # Initialize result matrix with -1 (unvisited marker)
10 distances = [[-1] * cols for _ in range(rows)]
11
12 # Queue for BFS traversal
13 queue = deque()
14
15 # Find all cells with 0 and mark them as starting points
16 for row in range(rows):
17 for col in range(cols):
18 if mat[row][col] == 0:
19 distances[row][col] = 0 # Distance to itself is 0
20 queue.append((row, col)) # Add to queue as BFS starting point
21
22 # Direction vectors for 4-directional movement (up, right, down, left)
23 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
24
25 # BFS to calculate minimum distances
26 while queue:
27 current_row, current_col = queue.popleft()
28
29 # Check all 4 adjacent cells
30 for row_offset, col_offset in directions:
31 next_row = current_row + row_offset
32 next_col = current_col + col_offset
33
34 # Check if the next cell is within bounds and unvisited
35 if (0 <= next_row < rows and
36 0 <= next_col < cols and
37 distances[next_row][next_col] == -1):
38
39 # Update distance: current cell's distance + 1
40 distances[next_row][next_col] = distances[current_row][current_col] + 1
41
42 # Add the newly visited cell to queue for further exploration
43 queue.append((next_row, next_col))
44
45 return distances
46
1class Solution {
2 public int[][] updateMatrix(int[][] mat) {
3 // Get matrix dimensions
4 int rows = mat.length;
5 int cols = mat[0].length;
6
7 // Initialize result matrix with -1 (unvisited marker)
8 int[][] distances = new int[rows][cols];
9 for (int[] row : distances) {
10 Arrays.fill(row, -1);
11 }
12
13 // Initialize BFS queue
14 Deque<int[]> queue = new ArrayDeque<>();
15
16 // Add all cells with 0 to the queue as starting points
17 // These cells have distance 0 to the nearest 0
18 for (int row = 0; row < rows; row++) {
19 for (int col = 0; col < cols; col++) {
20 if (mat[row][col] == 0) {
21 queue.offer(new int[] {row, col});
22 distances[row][col] = 0;
23 }
24 }
25 }
26
27 // Direction vectors for moving up, right, down, left
28 // Using a compact representation: dirs[i] and dirs[i+1] form a direction pair
29 int[] directions = {-1, 0, 1, 0, -1};
30
31 // Perform multi-source BFS
32 while (!queue.isEmpty()) {
33 // Get current cell coordinates
34 int[] currentCell = queue.poll();
35 int currentRow = currentCell[0];
36 int currentCol = currentCell[1];
37
38 // Explore all 4 adjacent cells
39 for (int dir = 0; dir < 4; dir++) {
40 int nextRow = currentRow + directions[dir];
41 int nextCol = currentCol + directions[dir + 1];
42
43 // Check if the adjacent cell is valid and unvisited
44 if (nextRow >= 0 && nextRow < rows &&
45 nextCol >= 0 && nextCol < cols &&
46 distances[nextRow][nextCol] == -1) {
47
48 // Update distance for the adjacent cell
49 distances[nextRow][nextCol] = distances[currentRow][currentCol] + 1;
50
51 // Add the adjacent cell to queue for further exploration
52 queue.offer(new int[] {nextRow, nextCol});
53 }
54 }
55 }
56
57 return distances;
58 }
59}
60
1class Solution {
2public:
3 vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
4 // Get matrix dimensions
5 int rows = mat.size();
6 int cols = mat[0].size();
7
8 // Initialize result matrix with -1 (unvisited)
9 vector<vector<int>> distances(rows, vector<int>(cols, -1));
10
11 // Queue for BFS traversal, stores (row, col) pairs
12 queue<pair<int, int>> bfsQueue;
13
14 // Find all cells with 0 and add them to queue as starting points
15 for (int row = 0; row < rows; ++row) {
16 for (int col = 0; col < cols; ++col) {
17 if (mat[row][col] == 0) {
18 distances[row][col] = 0; // Distance from 0 to itself is 0
19 bfsQueue.emplace(row, col);
20 }
21 }
22 }
23
24 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
25 vector<int> directions = {-1, 0, 1, 0, -1};
26
27 // BFS to calculate minimum distance to nearest 0 for each cell
28 while (!bfsQueue.empty()) {
29 // Get current cell from queue
30 auto [currentRow, currentCol] = bfsQueue.front();
31 bfsQueue.pop();
32
33 // Explore all 4 adjacent cells
34 for (int i = 0; i < 4; ++i) {
35 int nextRow = currentRow + directions[i];
36 int nextCol = currentCol + directions[i + 1];
37
38 // Check if next cell is valid and unvisited
39 if (nextRow >= 0 && nextRow < rows &&
40 nextCol >= 0 && nextCol < cols &&
41 distances[nextRow][nextCol] == -1) {
42
43 // Update distance: current cell's distance + 1
44 distances[nextRow][nextCol] = distances[currentRow][currentCol] + 1;
45
46 // Add to queue for further exploration
47 bfsQueue.emplace(nextRow, nextCol);
48 }
49 }
50 }
51
52 return distances;
53 }
54};
55
1/**
2 * Finds the distance of each cell to the nearest 0 in a binary matrix
3 * Uses multi-source BFS starting from all 0 cells simultaneously
4 * @param mat - Input matrix containing 0s and 1s
5 * @returns Matrix where each cell contains distance to nearest 0
6 */
7function updateMatrix(mat: number[][]): number[][] {
8 // Get matrix dimensions
9 const rows: number = mat.length;
10 const cols: number = mat[0].length;
11
12 // Initialize result matrix with -1 (unvisited marker)
13 const distanceMatrix: number[][] = Array.from(
14 { length: rows },
15 () => Array.from({ length: cols }, () => -1)
16 );
17
18 // Queue for BFS traversal, stores [row, col] coordinates
19 const bfsQueue: [number, number][] = [];
20
21 // Find all cells with 0 and add them as starting points for BFS
22 for (let row = 0; row < rows; row++) {
23 for (let col = 0; col < cols; col++) {
24 if (mat[row][col] === 0) {
25 bfsQueue.push([row, col]);
26 distanceMatrix[row][col] = 0; // Distance to itself is 0
27 }
28 }
29 }
30
31 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
32 // Using pairs: (dirs[k], dirs[k+1]) represents (deltaRow, deltaCol)
33 const directions: number[] = [-1, 0, 1, 0, -1];
34
35 // Perform BFS from all 0 cells simultaneously
36 for (const [currentRow, currentCol] of bfsQueue) {
37 // Check all 4 adjacent cells
38 for (let k = 0; k < 4; k++) {
39 const nextRow: number = currentRow + directions[k];
40 const nextCol: number = currentCol + directions[k + 1];
41
42 // Check if the adjacent cell is within bounds and unvisited
43 if (nextRow >= 0 && nextRow < rows &&
44 nextCol >= 0 && nextCol < cols &&
45 distanceMatrix[nextRow][nextCol] === -1) {
46
47 // Update distance as current cell's distance + 1
48 distanceMatrix[nextRow][nextCol] = distanceMatrix[currentRow][currentCol] + 1;
49
50 // Add to queue for further exploration
51 bfsQueue.push([nextRow, nextCol]);
52 }
53 }
54 }
55
56 return distanceMatrix;
57}
58
Time and Space Complexity
Time Complexity: O(m × n)
The algorithm uses BFS (Breadth-First Search) starting from all cells containing 0. Each cell in the matrix is visited at most once:
- Initially, we iterate through all
m × n
cells to find zeros and initialize the answer matrix, which takesO(m × n)
time - During BFS, each cell is added to the queue at most once (when
ans[x][y] == -1
is satisfied) - Each cell is processed (popped from queue) exactly once
- For each processed cell, we check at most 4 neighbors (constant time)
Therefore, the total time complexity is O(m × n)
.
Space Complexity: O(m × n)
The space usage includes:
- The
ans
matrix:O(m × n)
space to store the distances - The queue
q
: In the worst case (when all cells are 0), the queue could contain allm × n
cells initially, requiringO(m × n)
space - The
dirs
tuple uses constantO(1)
space
The dominant factor is O(m × n)
, making the overall space complexity O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Single-Source BFS Instead of Multi-Source BFS
A common mistake is attempting to solve this problem by running BFS from each 1
cell to find the nearest 0
. This approach leads to inefficiency and complexity.
Incorrect Approach:
# DON'T DO THIS - Inefficient single-source BFS
def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
rows, cols = len(mat), len(mat[0])
distances = [[0] * cols for _ in range(rows)]
for i in range(rows):
for j in range(cols):
if mat[i][j] == 1:
# Run BFS from this cell to find nearest 0
distances[i][j] = self.bfs_to_zero(mat, i, j)
return distances
Why it's problematic:
- Time complexity becomes O(m²n²) in the worst case
- Each
1
cell requires its own BFS traversal - Redundant computations as multiple cells may explore the same paths
Solution: Use multi-source BFS starting from all 0
s simultaneously, as shown in the correct implementation.
2. Incorrect Initialization of the Distance Matrix
Another pitfall is initializing the distance matrix incorrectly, such as using 0
or float('inf')
for unvisited cells.
Incorrect:
# Using 0 for unvisited cells - causes confusion with actual 0 distances
distances = [[0] * cols for _ in range(rows)]
# Later in BFS:
if distances[next_row][next_col] == 0: # Wrong! Can't distinguish between unvisited and distance-0 cells
distances[next_row][next_col] = distances[current_row][current_col] + 1
Why it's problematic:
- Using
0
makes it impossible to distinguish between cells containing0
(distance = 0) and unvisited cells - This leads to incorrect distance calculations or infinite loops
Solution: Use -1
or a separate visited matrix to track unvisited cells clearly.
3. Forgetting to Mark Cells as Visited Before Adding to Queue
Some implementations mark cells as visited after dequeuing rather than before enqueuing.
Incorrect:
while queue: current_row, current_col = queue.popleft() for row_offset, col_offset in directions: next_row = current_row + row_offset next_col = current_col + col_offset if (0 <= next_row < rows and 0 <= next_col < cols and distances[next_row][next_col] == -1): queue.append((next_row, next_col)) # Added to queue first distances[next_row][next_col] = distances[current_row][current_col] + 1 # Marked after
Why it's problematic:
- The same cell can be added to the queue multiple times from different neighbors
- This doesn't affect correctness (first visit will have minimum distance) but causes unnecessary processing
- Queue can grow larger than necessary, impacting space complexity
Solution: Always mark the cell as visited (update its distance) immediately before adding it to the queue, as shown in the correct implementation.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!