Facebook Pixel

1162. As Far from Land as Possible

Problem Description

You are given an n x n grid where each cell contains either 0 (representing water) or 1 (representing land). Your task is to find a water cell that is as far as possible from any land cell, and return this maximum distance.

The distance between two cells is calculated using Manhattan distance: for two cells at positions (x0, y0) and (x1, y1), the distance is |x0 - x1| + |y0 - y1|.

If the grid contains only water cells (no land) or only land cells (no water), return -1.

The solution uses a multi-source BFS approach. First, all land cells are added to a queue as starting points. If the queue is empty (no land) or contains all cells in the grid (no water), the function returns -1.

The BFS then proceeds level by level. In each iteration:

  • All cells at the current distance level are processed
  • For each cell, its four adjacent neighbors (up, down, left, right) are checked
  • If a neighbor is a water cell (value 0), it's marked as visited by changing it to 1 and added to the queue for the next level
  • After processing each level, the distance counter increases by 1

The process continues until all reachable water cells have been visited. The final distance value represents the maximum distance from any water cell to the nearest land cell.

The key insight is that by starting from all land cells simultaneously and expanding outward, the last water cells to be reached are necessarily the ones furthest from any land.

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 grid 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 traverse this graph to find distances.

Is it a tree?

  • No: The grid structure is not a tree - it can have multiple paths between cells and doesn't have a hierarchical parent-child relationship.

Is the problem related to directed acyclic graphs?

  • No: The grid allows movement in all four directions, and the problem isn't about directed edges or acyclic properties.

Is the problem related to shortest paths?

  • Yes: We need to find the shortest distance from each water cell to the nearest land cell. The maximum of these shortest distances is our answer.

Is the graph Weighted?

  • No: All edges have the same weight (distance of 1 between adjacent cells). The Manhattan distance is simply the count of steps taken.

BFS

  • Yes: Since we have an unweighted graph and need to find shortest paths, BFS is the appropriate algorithm.

Conclusion: The flowchart suggests using BFS for this problem. Specifically, we use multi-source BFS starting from all land cells simultaneously, which efficiently computes the shortest distance from each water cell to the nearest land cell. The last water cells reached by the BFS expansion will be the ones furthest from any land.

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

Intuition

The key insight is to reverse our thinking about the problem. Instead of finding the distance from each water cell to the nearest land, we can start from all land cells and expand outward simultaneously.

Think of it like this: imagine all land cells are sources of a "wave" that spreads outward at the same speed. Each wave moves one cell per time unit. Water cells get "hit" by the wave when it reaches them. The water cells that get hit last are necessarily the ones furthest from any land.

This multi-source BFS approach is efficient because:

  1. We only need to traverse the grid once, rather than running BFS from each water cell individually
  2. By starting from all land cells at once, we naturally compute the minimum distance to land for each water cell
  3. The last cells to be reached in our BFS traversal are guaranteed to be the furthest from any land

The algorithm works by:

  • Initially marking all land cells as "distance 0" by adding them to the queue
  • In each iteration, we process all cells at the current distance level
  • We mark newly reached water cells and add them to the queue for the next level
  • The number of levels (iterations) we go through equals the maximum distance

This transforms a potentially complex problem of finding maximum of minimums into a simple level-order traversal where the answer is just the number of levels we traverse.

Learn more about Breadth-First Search and Dynamic Programming patterns.

Solution Approach

The implementation uses a multi-source BFS approach with the following key components:

Data Structures:

  • A deque (double-ended queue) to store cells for BFS traversal
  • The original grid itself is modified to mark visited cells

Initial Setup:

q = deque((i, j) for i in range(n) for j in range(n) if grid[i][j])

We scan the entire grid and add all land cells (cells with value 1) to the queue. These serve as our starting points for the BFS.

Edge Case Handling:

if len(q) in (0, n * n):
    return -1

If the queue is empty (no land) or contains all cells (no water), we return -1 as specified.

BFS Traversal: The BFS proceeds level by level with ans initialized to -1:

while q:
    for _ in range(len(q)):
        i, j = q.popleft()
        for a, b in pairwise(dirs):
            x, y = i + a, j + b
            if 0 <= x < n and 0 <= y < n and grid[x][y] == 0:
                grid[x][y] = 1
                q.append((x, y))
    ans += 1

Direction Handling: The dirs = (-1, 0, 1, 0, -1) array with pairwise creates the four directional moves:

  • (-1, 0) for up
  • (0, 1) for right
  • (1, 0) for down
  • (0, -1) for left

Level Processing:

  • The inner loop for _ in range(len(q)) ensures we process all cells at the current distance level before moving to the next
  • When we find an unvisited water cell (grid[x][y] == 0), we mark it as visited by setting it to 1 and add it to the queue
  • After each level is completely processed, we increment ans

Space Optimization: Instead of using a separate visited array, the solution cleverly reuses the input grid to mark visited cells by changing water cells (0) to land cells (1) as they're visited.

The final ans represents the number of levels traversed, which equals the maximum distance from any water cell to the nearest land cell.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small 3x3 grid example to illustrate the multi-source BFS approach:

Initial Grid:
1 0 0
0 0 0  
0 0 1

Step 1: Initial Setup

  • Scan the grid and add all land cells (value 1) to the queue
  • Queue initially contains: [(0,0), (2,2)] - the two land cells
  • Since we have both land and water, we proceed with BFS
  • ans = -1

Step 2: First BFS Level (Distance 0)

  • Process all cells currently in queue: (0,0) and (2,2)
  • From (0,0), check neighbors:
    • Up: out of bounds
    • Right: (0,1) is water (0) → mark as visited (set to 1), add to queue
    • Down: (1,0) is water (0) → mark as visited (set to 1), add to queue
    • Left: out of bounds
  • From (2,2), check neighbors:
    • Up: (1,2) is water (0) → mark as visited (set to 1), add to queue
    • Right: out of bounds
    • Down: out of bounds
    • Left: (2,1) is water (0) → mark as visited (set to 1), add to queue
  • Queue for next level: [(0,1), (1,0), (1,2), (2,1)]
  • Increment ans = 0

Grid after Level 0:

1 1 0
1 0 1  
0 1 1

Step 3: Second BFS Level (Distance 1)

  • Process cells: (0,1), (1,0), (1,2), (2,1)
  • From (0,1):
    • Right: (0,2) is water → mark as visited, add to queue
  • From (1,0):
    • Right: (1,1) is water → mark as visited, add to queue
  • From (1,2):
    • Left: (1,1) already marked as visited this round
  • From (2,1):
    • Left: (2,0) is water → mark as visited, add to queue
  • Queue for next level: [(0,2), (1,1), (2,0)]
  • Increment ans = 1

Grid after Level 1:

1 1 1
1 1 1  
1 1 1

Step 4: Third BFS Level (Distance 2)

  • Process cells: (0,2), (1,1), (2,0)
  • All neighbors are already visited (marked as 1)
  • No new cells added to queue
  • Increment ans = 2

Step 5: Termination

  • Queue is empty, BFS completes
  • Return ans = 2

The cells (0,2), (1,1), and (2,0) were the last to be reached, confirming they are the furthest water cells from any land. Each has a Manhattan distance of 2 to the nearest land cell. For example, (1,1) is distance 2 from both (0,0) and (2,2).

Solution Implementation

1from collections import deque
2from typing import List
3
4class Solution:
5    def maxDistance(self, grid: List[List[int]]) -> int:
6        # Get grid dimensions
7        n = len(grid)
8      
9        # Initialize queue with all land cells (value = 1)
10        queue = deque()
11        for row in range(n):
12            for col in range(n):
13                if grid[row][col] == 1:
14                    queue.append((row, col))
15      
16        # If there's no land or no water, return -1
17        land_count = len(queue)
18        if land_count == 0 or land_count == n * n:
19            return -1
20      
21        # Direction vectors for moving up, right, down, left
22        directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
23      
24        # BFS to find maximum distance
25        max_distance = -1
26      
27        while queue:
28            # Process all cells at current distance level
29            current_level_size = len(queue)
30            for _ in range(current_level_size):
31                current_row, current_col = queue.popleft()
32              
33                # Check all 4 adjacent cells
34                for delta_row, delta_col in directions:
35                    next_row = current_row + delta_row
36                    next_col = current_col + delta_col
37                  
38                    # If adjacent cell is valid water cell, mark it as visited
39                    if (0 <= next_row < n and 
40                        0 <= next_col < n and 
41                        grid[next_row][next_col] == 0):
42                      
43                        grid[next_row][next_col] = 1  # Mark as visited
44                        queue.append((next_row, next_col))
45          
46            # Increment distance after processing each level
47            max_distance += 1
48      
49        return max_distance
50
1class Solution {
2    public int maxDistance(int[][] grid) {
3        int n = grid.length;
4        // Queue to store coordinates for BFS traversal
5        Deque<int[]> queue = new ArrayDeque<>();
6      
7        // Add all land cells (value = 1) to the queue as starting points
8        for (int row = 0; row < n; row++) {
9            for (int col = 0; col < n; col++) {
10                if (grid[row][col] == 1) {
11                    queue.offer(new int[] {row, col});
12                }
13            }
14        }
15      
16        // Initialize result to -1 (will be incremented for each BFS level)
17        int maxDist = -1;
18      
19        // Edge cases: all water or all land - no valid distance exists
20        if (queue.isEmpty() || queue.size() == n * n) {
21            return maxDist;
22        }
23      
24        // Direction vectors for moving up, right, down, left
25        int[] directions = {-1, 0, 1, 0, -1};
26      
27        // Multi-source BFS to find maximum distance from any land to water
28        while (!queue.isEmpty()) {
29            // Process all cells at current distance level
30            int currentLevelSize = queue.size();
31            for (int i = 0; i < currentLevelSize; i++) {
32                int[] currentCell = queue.poll();
33              
34                // Check all 4 adjacent cells
35                for (int dir = 0; dir < 4; dir++) {
36                    int nextRow = currentCell[0] + directions[dir];
37                    int nextCol = currentCell[1] + directions[dir + 1];
38                  
39                    // If adjacent cell is valid water cell, mark it as visited and add to queue
40                    if (nextRow >= 0 && nextRow < n && nextCol >= 0 && nextCol < n 
41                        && grid[nextRow][nextCol] == 0) {
42                        grid[nextRow][nextCol] = 1;  // Mark as visited
43                        queue.offer(new int[] {nextRow, nextCol});
44                    }
45                }
46            }
47            // Increment distance after processing each level
48            maxDist++;
49        }
50      
51        return maxDist;
52    }
53}
54
1class Solution {
2public:
3    int maxDistance(vector<vector<int>>& grid) {
4        int gridSize = grid.size();
5        queue<pair<int, int>> bfsQueue;
6      
7        // Add all land cells (1s) to the queue as starting points
8        for (int row = 0; row < gridSize; ++row) {
9            for (int col = 0; col < gridSize; ++col) {
10                if (grid[row][col] == 1) {
11                    bfsQueue.emplace(row, col);
12                }
13            }
14        }
15      
16        // Initialize result to -1 (will be incremented for each BFS level)
17        int maxDist = -1;
18      
19        // Edge cases: no water or no land
20        if (bfsQueue.empty() || bfsQueue.size() == gridSize * gridSize) {
21            return maxDist;
22        }
23      
24        // Direction vectors for moving up, right, down, left
25        int directions[5] = {-1, 0, 1, 0, -1};
26      
27        // Multi-source BFS to find maximum distance from any water cell to nearest land
28        while (!bfsQueue.empty()) {
29            int currentLevelSize = bfsQueue.size();
30          
31            // Process all cells at current distance level
32            for (int i = 0; i < currentLevelSize; ++i) {
33                auto [currentRow, currentCol] = bfsQueue.front();
34                bfsQueue.pop();
35              
36                // Explore all 4 adjacent cells
37                for (int dir = 0; dir < 4; ++dir) {
38                    int nextRow = currentRow + directions[dir];
39                    int nextCol = currentCol + directions[dir + 1];
40                  
41                    // Check if the adjacent cell is valid water cell
42                    if (nextRow >= 0 && nextRow < gridSize && 
43                        nextCol >= 0 && nextCol < gridSize && 
44                        grid[nextRow][nextCol] == 0) {
45                      
46                        // Mark water cell as visited by setting it to 1
47                        grid[nextRow][nextCol] = 1;
48                        bfsQueue.emplace(nextRow, nextCol);
49                    }
50                }
51            }
52          
53            // Increment distance after processing each level
54            ++maxDist;
55        }
56      
57        return maxDist;
58    }
59};
60
1/**
2 * Finds the maximum Manhattan distance from any water cell (0) to the nearest land cell (1)
3 * Uses multi-source BFS starting from all land cells simultaneously
4 * @param grid - 2D array where 1 represents land and 0 represents water
5 * @returns Maximum distance from water to nearest land, or -1 if no valid configuration exists
6 */
7function maxDistance(grid: number[][]): number {
8    const gridSize: number = grid.length;
9    const queue: Array<[number, number]> = [];
10  
11    // Find all land cells and add them to the queue as starting points
12    for (let row = 0; row < gridSize; row++) {
13        for (let col = 0; col < gridSize; col++) {
14            if (grid[row][col] === 1) {
15                queue.push([row, col]);
16            }
17        }
18    }
19  
20    // Initialize the answer to -1 (default return value for invalid cases)
21    let maxDistanceFound: number = -1;
22  
23    // If there's no land or no water, return -1
24    if (queue.length === 0 || queue.length === gridSize * gridSize) {
25        return maxDistanceFound;
26    }
27  
28    // Direction vectors for moving up, right, down, left
29    const directions: number[] = [-1, 0, 1, 0, -1];
30  
31    // BFS to find the maximum distance
32    while (queue.length > 0) {
33        // Process all cells at the current distance level
34        const currentLevelSize: number = queue.length;
35      
36        for (let i = 0; i < currentLevelSize; i++) {
37            const [currentRow, currentCol] = queue.shift()!;
38          
39            // Check all 4 adjacent cells
40            for (let dirIndex = 0; dirIndex < 4; dirIndex++) {
41                const nextRow: number = currentRow + directions[dirIndex];
42                const nextCol: number = currentCol + directions[dirIndex + 1];
43              
44                // If the adjacent cell is valid water cell, mark it as visited and add to queue
45                if (nextRow >= 0 && nextRow < gridSize && 
46                    nextCol >= 0 && nextCol < gridSize && 
47                    grid[nextRow][nextCol] === 0) {
48                  
49                    grid[nextRow][nextCol] = 1; // Mark as visited
50                    queue.push([nextRow, nextCol]);
51                }
52            }
53        }
54      
55        // Increment distance after processing each level
56        maxDistanceFound++;
57    }
58  
59    return maxDistanceFound;
60}
61

Time and Space Complexity

Time Complexity: O(n²)

The algorithm uses BFS (Breadth-First Search) starting from all land cells simultaneously. In the worst case, every cell in the n × n grid will be visited exactly once. The initial queue construction takes O(n²) time to iterate through all cells. During the BFS traversal, each cell is added to the queue at most once and processed once, resulting in O(n²) operations. For each cell processed, we check 4 directions, which is O(1) per cell. Therefore, the overall time complexity is O(n²).

Space Complexity: O(n²)

The space complexity is dominated by the queue q which, in the worst case, can contain all cells of the grid. This happens when all cells are initially land cells (containing 1), making the queue size n × n. Even in a more typical case where the queue grows during BFS execution, it can still hold up to O(n²) cells at once. The dirs tuple uses constant space O(1). Since we're modifying the input grid in-place, no additional grid space is needed. Therefore, the overall space complexity is O(n²).

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

Common Pitfalls

1. Incorrect Distance Initialization

A common mistake is initializing the distance counter to 0 instead of -1. Since we increment the distance after processing each level (including the initial land cells), starting at 0 would give us a result that's off by one.

Wrong:

max_distance = 0  # Incorrect initialization
while queue:
    # ... BFS logic ...
    max_distance += 1
return max_distance  # Returns distance + 1

Correct:

max_distance = -1  # Correct initialization
while queue:
    # ... BFS logic ...
    max_distance += 1
return max_distance

2. Not Processing Level by Level

Failing to process all cells at the current distance level before moving to the next level will result in incorrect distance calculations. The inner loop that processes len(queue) cells is crucial.

Wrong:

while queue:
    current_row, current_col = queue.popleft()  # Processes cells one by one
    for delta_row, delta_col in directions:
        # ... check neighbors ...
    max_distance += 1  # Increments for each cell, not each level!

Correct:

while queue:
    current_level_size = len(queue)
    for _ in range(current_level_size):  # Process entire level
        current_row, current_col = queue.popleft()
        # ... check neighbors ...
    max_distance += 1  # Increments once per level

3. Modifying the Queue Size During Iteration

Using range(len(queue)) directly without storing the size first can lead to issues if the queue is modified during iteration.

Wrong:

for _ in range(len(queue)):  # If queue is modified, this could behave unexpectedly
    # ... process cells and add to queue ...

Correct:

current_level_size = len(queue)  # Store size before iteration
for _ in range(current_level_size):
    # ... process cells and add to queue ...

4. Using a Separate Visited Array Unnecessarily

While not incorrect, using additional space for tracking visited cells is unnecessary since we can modify the grid in-place. This wastes O(n²) extra space.

Inefficient:

visited = [[False] * n for _ in range(n)]
# ... in BFS loop ...
if not visited[next_row][next_col] and grid[next_row][next_col] == 0:
    visited[next_row][next_col] = True
    queue.append((next_row, next_col))

Efficient:

# Reuse the grid itself
if grid[next_row][next_col] == 0:
    grid[next_row][next_col] = 1  # Mark as visited by changing value
    queue.append((next_row, next_col))

5. Forgetting Edge Case Validation

Not checking for grids with all water or all land will cause the BFS to either never start or return an incorrect result.

Wrong:

# Missing edge case check
queue = deque()
for row in range(n):
    for col in range(n):
        if grid[row][col] == 1:
            queue.append((row, col))
# Proceeds with BFS even if queue is empty or full

Correct:

land_count = len(queue)
if land_count == 0 or land_count == n * n:
    return -1  # Handle edge cases before BFS
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More