Facebook Pixel

1030. Matrix Cells in Distance Order

Problem Description

You have a matrix with dimensions rows x cols, and you start at a specific cell located at coordinates (rCenter, cCenter).

Your task is to return a list containing the coordinates of all cells in the matrix, but arranged in a specific order: they must be sorted by their Manhattan distance from your starting position (rCenter, cCenter), from smallest to largest distance.

The Manhattan distance between any two cells (r1, c1) and (r2, c2) is calculated as: |r1 - r2| + |c1 - c2|

For example, if you have a 3x3 matrix and start at position (1, 1) (the center), the cells would be ordered as:

  • Distance 0: [1, 1] (the starting cell itself)
  • Distance 1: [0, 1], [1, 0], [1, 2], [2, 1] (cells one step away)
  • Distance 2: [0, 0], [0, 2], [2, 0], [2, 2] (cells two steps away)

When multiple cells have the same distance from the center, they can be returned in any order as long as all cells with the same distance appear together in the correct distance group.

The solution uses a BFS (Breadth-First Search) approach with a queue. Starting from (rCenter, cCenter), it explores cells layer by layer - first visiting all cells at distance 1, then distance 2, and so on. A visited array tracks which cells have been processed to avoid duplicates. The pairwise function with (-1, 0, 1, 0, -1) generates the four directional movements: up, right, down, and left.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to visit all cells sorted by their distance from a starting point, we can think about how distances naturally expand outward like ripples in water. If we drop a stone at (rCenter, cCenter), the ripples would reach cells at distance 1 first, then distance 2, then distance 3, and so on.

This pattern immediately suggests BFS (Breadth-First Search) because BFS explores nodes level by level - it visits all nodes at distance k before visiting any node at distance k+1. This is exactly what we need!

Starting from our center cell, we can think of the problem as exploring a graph where:

  • Each cell is a node
  • Each cell is connected to its 4 adjacent neighbors (up, down, left, right)
  • Moving from one cell to an adjacent cell increases the Manhattan distance by exactly 1

By using BFS with a queue, we naturally process cells in order of increasing distance:

  1. Start with the center cell (distance 0)
  2. Add all unvisited neighbors (distance 1) to the queue
  3. Process all cells at distance 1, adding their unvisited neighbors (distance 2)
  4. Continue this pattern until all cells are visited

The key insight is that BFS guarantees we'll visit cells in non-decreasing order of their distance from the starting point. We don't need to explicitly calculate or sort by distances - the BFS traversal order automatically gives us the correct sorting!

We use a vis (visited) array to ensure each cell is added to our result exactly once. The directional movements (-1, 0, 1, 0, -1) paired together give us the four directions: up (-1, 0), right (0, 1), down (1, 0), and left (0, -1).

Learn more about Math and Sorting patterns.

Solution Approach

The implementation uses BFS with a queue to traverse the matrix layer by layer from the center point:

1. Initialization:

  • Create a queue q and initialize it with the starting cell [rCenter, cCenter]
  • Create a 2D boolean array vis of size rows x cols to track visited cells, initially all False
  • Mark the starting cell as visited: vis[rCenter][cCenter] = True
  • Create an empty list ans to store the result

2. BFS Traversal:

while q:
    for _ in range(len(q)):  # Process all cells at current distance
        p = q.popleft()       # Get next cell
        ans.append(p)         # Add to result

The outer while loop continues until the queue is empty. The inner for loop processes all cells at the current distance level before moving to the next distance level.

3. Exploring Neighbors:

for a, b in pairwise((-1, 0, 1, 0, -1)):
    x, y = p[0] + a, p[1] + b

The pairwise function with (-1, 0, 1, 0, -1) generates pairs:

  • (-1, 0) → move up
  • (0, 1) → move right
  • (1, 0) → move down
  • (0, -1) → move left

For each direction, we calculate the new coordinates (x, y).

4. Boundary Check and Visit:

if 0 <= x < rows and 0 <= y < cols and not vis[x][y]:
    vis[x][y] = True
    q.append([x, y])

For each neighbor:

  • Check if it's within matrix bounds
  • Check if it hasn't been visited
  • If both conditions are met, mark it as visited and add to queue

Why This Works:

BFS naturally processes cells in layers:

  • Layer 0: Just the center cell (distance 0)
  • Layer 1: All cells one step away (distance 1)
  • Layer 2: All cells two steps away (distance 2)
  • And so on...

Since each move to an adjacent cell increases Manhattan distance by exactly 1, BFS guarantees we visit cells in order of increasing distance. The vis array prevents revisiting cells, ensuring each cell appears exactly once in the result.

Time Complexity: O(rows × cols) - we visit each cell exactly once Space Complexity: O(rows × cols) - for the visited array and queue

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with a 3×3 matrix where we start at position (1, 1):

Matrix:
[0,0] [0,1] [0,2]
[1,0] [1,1] [1,2]
[2,0] [2,1] [2,2]

Starting position: (1, 1) - the center cell

Initial Setup:

  • Queue: [[1, 1]]
  • Visited array: All False except vis[1][1] = True
  • Result: []

Round 1 (Distance 0):

  • Process [1, 1] from queue
  • Add [1, 1] to result
  • Check 4 neighbors:
    • Up: (0, 1) - valid, not visited → mark visited, add to queue
    • Right: (1, 2) - valid, not visited → mark visited, add to queue
    • Down: (2, 1) - valid, not visited → mark visited, add to queue
    • Left: (1, 0) - valid, not visited → mark visited, add to queue
  • Queue: [[0, 1], [1, 2], [2, 1], [1, 0]]
  • Result: [[1, 1]]

Round 2 (Distance 1): Process all 4 cells currently in queue:

  1. Process [0, 1]:

    • Add to result
    • Check neighbors: (0, 0) and (0, 2) are unvisited → add to queue
  2. Process [1, 2]:

    • Add to result
    • Check neighbors: (0, 2) already visited, (2, 2) unvisited → add to queue
  3. Process [2, 1]:

    • Add to result
    • Check neighbors: (2, 0) and (2, 2) - add (2, 0) (2,2 already in queue)
  4. Process [1, 0]:

    • Add to result
    • Check neighbors: (0, 0) already in queue, (2, 0) already in queue
  • Queue: [[0, 0], [0, 2], [2, 2], [2, 0]]
  • Result: [[1, 1], [0, 1], [1, 2], [2, 1], [1, 0]]

Round 3 (Distance 2): Process all 4 corner cells:

  • Each cell is processed and added to result
  • No new unvisited neighbors (all cells have been visited)
  • Queue becomes empty
  • Result: [[1, 1], [0, 1], [1, 2], [2, 1], [1, 0], [0, 0], [0, 2], [2, 2], [2, 0]]

Final Result Verification:

  • Distance 0: [1, 1]
  • Distance 1: [0, 1], [1, 2], [2, 1], [1, 0]
  • Distance 2: [0, 0], [0, 2], [2, 2], [2, 0]

The BFS approach naturally groups cells by their Manhattan distance without explicitly calculating distances!

Solution Implementation

1from collections import deque
2from typing import List
3
4class Solution:
5    def allCellsDistOrder(
6        self, rows: int, cols: int, rCenter: int, cCenter: int
7    ) -> List[List[int]]:
8        """
9        Return all cells in a rows x cols matrix, sorted by their Manhattan distance 
10        from (rCenter, cCenter) in non-decreasing order.
11      
12        Uses BFS to traverse cells layer by layer from the center.
13        """
14        # Initialize queue with the center cell
15        queue = deque([[rCenter, cCenter]])
16      
17        # Create visited matrix to track processed cells
18        visited = [[False] * cols for _ in range(rows)]
19        visited[rCenter][cCenter] = True
20      
21        # Result list to store cells in order of distance
22        result = []
23      
24        # BFS traversal
25        while queue:
26            # Process all cells at current distance level
27            level_size = len(queue)
28            for _ in range(level_size):
29                current_cell = queue.popleft()
30                result.append(current_cell)
31              
32                # Check all 4 adjacent cells (up, right, down, left)
33                # Using pairwise to create direction pairs: (-1,0), (0,1), (1,0), (0,-1)
34                for delta_row, delta_col in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
35                    new_row = current_cell[0] + delta_row
36                    new_col = current_cell[1] + delta_col
37                  
38                    # Check if the new cell is within bounds and not visited
39                    if (0 <= new_row < rows and 
40                        0 <= new_col < cols and 
41                        not visited[new_row][new_col]):
42                        visited[new_row][new_col] = True
43                        queue.append([new_row, new_col])
44      
45        return result
46
1class Solution {
2    public int[][] allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
3        // Initialize BFS queue with the center cell
4        Deque<int[]> queue = new ArrayDeque<>();
5        queue.offer(new int[] {rCenter, cCenter});
6      
7        // Track visited cells to avoid duplicates
8        boolean[][] visited = new boolean[rows][cols];
9        visited[rCenter][cCenter] = true;
10      
11        // Result array to store all cells in order of distance
12        int[][] result = new int[rows * cols][2];
13      
14        // Direction vectors for moving up, right, down, left
15        // Using the pattern: (row-1, col), (row, col+1), (row+1, col), (row, col-1)
16        int[] directions = {-1, 0, 1, 0, -1};
17      
18        // Index to track position in result array
19        int resultIndex = 0;
20      
21        // BFS traversal - cells are naturally ordered by distance
22        while (!queue.isEmpty()) {
23            // Process all cells at current distance level
24            int levelSize = queue.size();
25            for (int i = 0; i < levelSize; i++) {
26                // Get current cell coordinates
27                int[] currentCell = queue.poll();
28              
29                // Add current cell to result
30                result[resultIndex++] = currentCell;
31              
32                // Explore all 4 adjacent cells
33                for (int k = 0; k < 4; k++) {
34                    int newRow = currentCell[0] + directions[k];
35                    int newCol = currentCell[1] + directions[k + 1];
36                  
37                    // Check if the new cell is within bounds and not visited
38                    if (newRow >= 0 && newRow < rows && 
39                        newCol >= 0 && newCol < cols && 
40                        !visited[newRow][newCol]) {
41                      
42                        // Mark as visited and add to queue
43                        visited[newRow][newCol] = true;
44                        queue.offer(new int[] {newRow, newCol});
45                    }
46                }
47            }
48        }
49      
50        return result;
51    }
52}
53
1class Solution {
2public:
3    vector<vector<int>> allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
4        // Initialize BFS queue with the center cell
5        queue<pair<int, int>> bfsQueue;
6        bfsQueue.emplace(rCenter, cCenter);
7      
8        // Result vector to store cells in order of increasing distance
9        vector<vector<int>> result;
10      
11        // Visited matrix to track which cells have been processed
12        bool visited[rows][cols];
13        memset(visited, false, sizeof(visited));
14        visited[rCenter][cCenter] = true;
15      
16        // Direction vectors for moving up, right, down, left
17        // Using the pattern: (row_offset[i], col_offset[i]) for i = 0 to 3
18        int directionOffsets[5] = {-1, 0, 1, 0, -1};
19      
20        // BFS traversal to visit cells level by level (same distance cells together)
21        while (!bfsQueue.empty()) {
22            // Process all cells at current distance level
23            int currentLevelSize = bfsQueue.size();
24            for (int i = 0; i < currentLevelSize; ++i) {
25                // Get and remove the front cell from queue
26                auto [currentRow, currentCol] = bfsQueue.front();
27                bfsQueue.pop();
28              
29                // Add current cell to result
30                result.push_back({currentRow, currentCol});
31              
32                // Explore all 4 adjacent cells
33                for (int direction = 0; direction < 4; ++direction) {
34                    int nextRow = currentRow + directionOffsets[direction];
35                    int nextCol = currentCol + directionOffsets[direction + 1];
36                  
37                    // Check if the adjacent cell is valid and unvisited
38                    if (nextRow >= 0 && nextRow < rows && 
39                        nextCol >= 0 && nextCol < cols && 
40                        !visited[nextRow][nextCol]) {
41                        // Mark as visited and add to queue for next level
42                        visited[nextRow][nextCol] = true;
43                        bfsQueue.emplace(nextRow, nextCol);
44                    }
45                }
46            }
47        }
48      
49        return result;
50    }
51};
52
1function allCellsDistOrder(rows: number, cols: number, rCenter: number, cCenter: number): number[][] {
2    // Initialize BFS queue with the center cell
3    const bfsQueue: [number, number][] = [];
4    bfsQueue.push([rCenter, cCenter]);
5  
6    // Result array to store cells in order of increasing distance
7    const result: number[][] = [];
8  
9    // Visited matrix to track which cells have been processed
10    const visited: boolean[][] = Array(rows).fill(null).map(() => Array(cols).fill(false));
11    visited[rCenter][cCenter] = true;
12  
13    // Direction vectors for moving up, right, down, left
14    // Using the pattern: (rowOffset[i], colOffset[i]) for i = 0 to 3
15    const directionOffsets: number[] = [-1, 0, 1, 0, -1];
16  
17    // BFS traversal to visit cells level by level (same distance cells together)
18    while (bfsQueue.length > 0) {
19        // Process all cells at current distance level
20        const currentLevelSize = bfsQueue.length;
21        for (let i = 0; i < currentLevelSize; i++) {
22            // Get and remove the front cell from queue
23            const [currentRow, currentCol] = bfsQueue.shift()!;
24          
25            // Add current cell to result
26            result.push([currentRow, currentCol]);
27          
28            // Explore all 4 adjacent cells
29            for (let direction = 0; direction < 4; direction++) {
30                const nextRow = currentRow + directionOffsets[direction];
31                const nextCol = currentCol + directionOffsets[direction + 1];
32              
33                // Check if the adjacent cell is valid and unvisited
34                if (nextRow >= 0 && nextRow < rows && 
35                    nextCol >= 0 && nextCol < cols && 
36                    !visited[nextRow][nextCol]) {
37                    // Mark as visited and add to queue for next level
38                    visited[nextRow][nextCol] = true;
39                    bfsQueue.push([nextRow, nextCol]);
40                }
41            }
42        }
43    }
44  
45    return result;
46}
47

Time and Space Complexity

Time Complexity: O(rows * cols)

The algorithm uses BFS (Breadth-First Search) to traverse all cells in the matrix starting from the center position. Each cell is visited exactly once due to the visited array vis. For each cell, we perform constant-time operations:

  • Dequeuing from the queue: O(1)
  • Appending to the answer list: O(1)
  • Checking 4 adjacent cells using pairwise iteration: O(4) = O(1)
  • Marking cells as visited and enqueuing: O(1)

Since we visit all rows * cols cells exactly once, the total time complexity is O(rows * cols).

Space Complexity: O(rows * cols)

The space complexity consists of:

  • The vis (visited) array: O(rows * cols) to track which cells have been visited
  • The queue q: In the worst case, the queue can contain O(rows * cols) elements (though in practice it's much smaller due to BFS level-by-level processing)
  • The answer array ans: O(rows * cols) to store all cell coordinates
  • The temporary list p and variables: O(1)

The dominant factor is the space needed for the visited array and the answer array, giving us a total space complexity of O(rows * cols).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Processing Cells Level by Level

A critical mistake is forgetting to process all cells at the current distance before moving to the next distance level. Without the inner loop that processes len(queue) cells, the BFS would mix cells from different distances:

Incorrect Implementation:

while queue:
    current_cell = queue.popleft()
    result.append(current_cell)
    # Add neighbors to queue...

This would process cells in the order they're added to the queue, not strictly by distance layers.

Correct Implementation:

while queue:
    level_size = len(queue)
    for _ in range(level_size):  # Process all cells at current distance
        current_cell = queue.popleft()
        result.append(current_cell)
        # Add neighbors to queue...

2. Marking Cells as Visited After Dequeuing

A common BFS mistake is marking cells as visited when they're dequeued rather than when they're enqueued. This causes duplicate cells in the queue:

Incorrect Pattern:

while queue:
    current_cell = queue.popleft()
    if visited[current_cell[0]][current_cell[1]]:
        continue
    visited[current_cell[0]][current_cell[1]] = True  # Too late!
    result.append(current_cell)

This allows the same cell to be added to the queue multiple times from different neighbors, leading to:

  • Duplicate cells in the result
  • Inefficient processing
  • Potentially incorrect ordering

Correct Pattern:

if not visited[new_row][new_col]:
    visited[new_row][new_col] = True  # Mark immediately when adding
    queue.append([new_row, new_col])

3. Using Wrong Distance Calculation

Some might try to optimize by calculating actual Manhattan distances and sorting, which is unnecessary and error-prone:

Overcomplicated Approach:

all_cells = []
for r in range(rows):
    for c in range(cols):
        dist = abs(r - rCenter) + abs(c - cCenter)
        all_cells.append((dist, [r, c]))
all_cells.sort()
return [cell for _, cell in all_cells]

While this works, it's less efficient (O(n log n) sorting) and doesn't leverage the natural ordering that BFS provides.

4. Incorrect Direction Arrays

Using inconsistent or incorrect direction arrays can miss cells or cause index errors:

Common Mistakes:

# Mistake 1: Only 2 directions
directions = [(0, 1), (1, 0)]  # Missing up and left

# Mistake 2: Diagonal movements (wrong for Manhattan distance)
directions = [(1, 1), (-1, -1), (1, -1), (-1, 1)]

# Mistake 3: Typos in coordinates
directions = [(1, 0), (0, 1), (-1, 0), (0, 1)]  # Duplicate (0,1)

Correct Implementation:

directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]  # All 4 cardinal directions
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More