As Far from Land as Possible
MedPrompt
Given an n x n grid containing only values 0 and 1, where 0 represents water and 1 represents land, find a water cell such that its distance to the nearest land cell is maximized, and return the distance. If no land or water exists in the grid, return -1.
The distance used in this problem is the Manhattan distance: the distance between two cells (x0, y0) and (x1, y1) is |x0 - x1| + |y0 - y1|.
Example 1:
Input: grid = [[1,0,1],[0,0,0],[1,0,1]]
Output: 2
Explanation: The cell (1, 1) is as far as possible from all the land with distance 2.Example 2:
Input: grid = [[1,0,0],[0,0,0],[0,0,0]]
Output: 4
Explanation: The cell (2, 2) is as far as possible from all the land with distance 4.
Constraints:
n == grid.lengthn == grid[i].length1 <= n <= 100grid[i][j]is0or1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through every water cell in the grid. For each water cell, it then calculates the Manhattan distance to every land cell to find the nearest one. The maximum of these nearest distances is the result.
Algorithm
- Create a list
landCellsand a listwaterCells. - Iterate through the
gridfrom(0,0)to(n-1, n-1):- If
grid[i][j] == 1, add(i, j)tolandCells. - Else, add
(i, j)towaterCells.
- If
- If
landCellsorwaterCellsis empty, return -1. - Initialize
maxDist = 0. - For each
watercell inwaterCells:- Initialize
minDist = Integer.MAX_VALUE. - For each
landcell inlandCells:- Calculate
dist = |water.x - land.x| + |water.y - land.y|. - Update
minDist = min(minDist, dist).
- Calculate
- Update
maxDist = max(maxDist, minDist).
- Initialize
- Return
maxDist.
Walkthrough
First, we collect the coordinates of all land cells and all water cells into two separate lists. We handle the edge cases where there are no land cells or no water cells, returning -1 as required. We then iterate through each water cell. For each water cell, we initialize its minimum distance to a land cell to infinity. Inside this loop, we have another loop that iterates through all land cells. We calculate the Manhattan distance between the current water cell and the current land cell. We update the minimum distance for the current water cell if the newly calculated distance is smaller. After checking all land cells, we have the shortest distance from the current water cell to any land. We then update our overall maximum distance found so far. After iterating through all water cells, the overall maximum distance is our answer.
class Solution { public int maxDistance(int[][] grid) { int n = grid.length; java.util.List<int[]> landCells = new java.util.ArrayList<>(); java.util.List<int[]> waterCells = new java.util.ArrayList<>(); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { landCells.add(new int[]{i, j}); } else { waterCells.add(new int[]{i, j}); } } } if (landCells.isEmpty() || waterCells.isEmpty()) { return -1; } int maxDist = 0; for (int[] water : waterCells) { int minDist = Integer.MAX_VALUE; for (int[] land : landCells) { int dist = Math.abs(water[0] - land[0]) + Math.abs(water[1] - land[1]); minDist = Math.min(minDist, dist); } maxDist = Math.max(maxDist, minDist); } return maxDist; }}Complexity
Time
O(N^4), where N is the side length of the grid. Let L be the number of land cells and W be the number of water cells. The complexity is O(L * W). In the worst case, L and W can both be on the order of O(N^2), leading to an overall complexity of O(N^4). This is too slow for the given constraints.
Space
O(N^2), to store the coordinates of all land and water cells.
Trade-offs
Pros
Conceptually simple and easy to follow.
Cons
Extremely inefficient, leading to a 'Time Limit Exceeded' error on larger grids.
A much more efficient approach is to rephrase the problem. Instead of finding the nearest land for each water cell, we can find the nearest water cell for each land cell. By starting a single BFS simultaneously from all land cells, we can explore the grid in layers. The distance of the last visited water cell will be the maximum distance we are looking for.
Algorithm
- Initialize a queue and add the coordinates of all land cells (
1) from the grid. - Check for edge cases: if the queue is empty (no land) or its size is
n*n(all land), return -1. - Initialize
distance = -1. - While the queue is not empty:
- Increment
distance. - Get the number of nodes at the current level,
levelSize = queue.size(). - For
ifrom0tolevelSize - 1:- Dequeue a cell
(r, c). - For each of its four neighbors
(nr, nc):- If the neighbor is within bounds and is a water cell (
grid[nr][nc] == 0):- Mark the neighbor as visited by setting
grid[nr][nc] = 1. - Enqueue the neighbor
(nr, nc).
- Mark the neighbor as visited by setting
- If the neighbor is within bounds and is a water cell (
- Dequeue a cell
- Increment
- Return
distance.
Walkthrough
This method is known as a Multi-Source BFS. We treat all land cells as the initial sources for our search. First, we iterate through the grid and add the coordinates of all land cells (1s) to a queue. These cells are at distance 0 from the nearest land (themselves). We handle the edge cases: if there are no land cells (queue is empty) or no water cells (all cells are land), we return -1. We then start the BFS process. The search proceeds in levels. In each level, we process all the cells currently in the queue. For each cell we dequeue, we explore its four neighbors (up, down, left, right). If a neighbor is a water cell (0) and has not been visited yet, we mark it as visited (e.g., by changing its value in the grid to 1) and add it to the queue for the next level's processing. The distance is tracked by the levels of the BFS. We start with a distance of -1 and increment it for each level we process. The final distance value after the BFS completes is the maximum distance from any water cell to the nearest land. The BFS guarantees that we find the shortest path from any source. Since we start from all land cells, the distance at which we reach a water cell (r, c) is its shortest distance to any of the land cells. The last cell to be visited is, by definition, the farthest one.
class Solution { public int maxDistance(int[][] grid) { int n = grid.length; java.util.Queue<int[]> queue = new java.util.LinkedList<>(); // Add all land cells to the queue for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { queue.offer(new int[]{i, j}); } } } // Edge cases: no land or all land if (queue.isEmpty() || queue.size() == n * n) { return -1; } int distance = -1; int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; while (!queue.isEmpty()) { distance++; int levelSize = queue.size(); for (int i = 0; i < levelSize; i++) { int[] cell = queue.poll(); int r = cell[0]; int c = cell[1]; for (int[] dir : directions) { int newR = r + dir[0]; int newC = c + dir[1]; if (newR >= 0 && newR < n && newC >= 0 && newC < n && grid[newR][newC] == 0) { grid[newR][newC] = 1; // Mark as visited queue.offer(new int[]{newR, newC}); } } } } return distance; }}Complexity
Time
O(N^2), where N is the side length of the grid. Each cell is enqueued and dequeued at most once. The initial scan to find land cells also takes O(N^2).
Space
O(N^2), for the queue. In the worst-case scenario (e.g., a checkerboard pattern), the queue could hold up to half of the grid's cells.
Trade-offs
Pros
Optimal time complexity.
Efficiently solves the problem by reversing the perspective.
Cons
Modifies the input grid. A copy of the grid or a separate
visitedmatrix could be used to avoid this, at the cost of extra space.
Solutions
Solution
class Solution {public int maxDistance(int[][] grid) { int n = grid.length; Deque<int[]> q = new ArrayDeque<>(); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 1) { q.offer(new int[]{i, j}); } } } int ans = -1; if (q.isEmpty() || q.size() == n * n) { return ans; } int[] dirs = {-1, 0, 1, 0, -1}; while (!q.isEmpty()) { for (int i = q.size(); i > 0; --i) { int[] p = q.poll(); for (int k = 0; k < 4; ++k) { int x = p[0] + dirs[k], y = p[1] + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y] == 0) { grid[x][y] = 1; q.offer(new int[]{x, y}); } } } ++ans; } return ans; }}class Solution: def maxDistance(self, grid: List[List[int]]) -> int: n = len(grid) q = deque((i, j) for i in range(n) for j in range(n) if grid[i][j]) ans = - 1 if len(q) in (0, n * n): return ans dirs = (- 1, 0, 1, 0, - 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 return ansclass Solution {public: int maxDistance(vector<vector<int>> &grid) { int n = grid.size(); queue<pair<int, int>> q; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j]) { q.emplace(i, j); } } } int ans = -1; if (q.empty() || q.size() == n * n) { return ans; } int dirs[5] = {-1, 0, 1, 0, -1}; while (!q.empty()) { for (int m = q.size(); m; --m) { auto [i, j] = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && !grid[x][y]) { grid[x][y] = 1; q.emplace(x, y); } } } ++ans; } return ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.