questions A (01-matrix - avoid-flood)

This commit is contained in:
2025-05-24 21:40:39 +01:00
parent e8898841cf
commit f757e28b24
55 changed files with 10813 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
title: As Far from Land as Possible
slug: as-far-from-land-as-possible
difficulty: medium
leetcode_id: 1162
leetcode_url: https://leetcode.com/problems/as-far-from-land-as-possible/
categories:
- arrays
- graphs
patterns:
- bfs
- matrix-traversal
description: |
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|`.
constraints: |
- `n == grid.length`
- `n == grid[i].length`
- `1 <= n <= 100`
- `grid[i][j]` is `0` or `1`
examples:
- 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."
- 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."
explanation:
intuition: |
Imagine you're standing on each piece of land simultaneously and start walking outward in all four directions at the same pace. As you expand, you mark each water cell with the number of steps it took to reach it from the nearest land.
The key insight is that instead of calculating the distance from each water cell to its nearest land (which would require checking every water cell against every land cell), we **flip the perspective**: start from all land cells at once and expand outward.
Think of it like dropping ink into water at every land position simultaneously. The ink spreads outward one step at a time. The last water cell to be reached by any ink droplet is the one farthest from all land — and that's our answer.
This is a classic **multi-source BFS** problem. By starting BFS from all land cells together (distance 0), we guarantee that each water cell gets marked with its distance to the *nearest* land cell. The maximum distance we record is our answer.
approach: |
We solve this using **Multi-Source BFS**:
**Step 1: Initialise the queue with all land cells**
- Scan the entire grid to find all cells with value `1` (land)
- Add each land cell to our BFS queue with distance `0`
- If there are no land cells or no water cells, return `-1` immediately
&nbsp;
**Step 2: Perform BFS expansion**
- Process cells level by level (this ensures we explore all cells at distance `d` before moving to distance `d + 1`)
- For each cell, explore its four neighbours (up, down, left, right)
- If a neighbour is water (`0`) and hasn't been visited, mark it with the current distance + 1 and add it to the queue
- We can mark visited cells by changing their value in the grid (e.g., setting to the distance or a special value)
&nbsp;
**Step 3: Track the maximum distance**
- Each time we reach a new water cell, we record its distance
- The last level of BFS gives us the farthest water cells
- Return the maximum distance found
&nbsp;
This approach works because BFS explores in order of increasing distance. Starting from all land simultaneously means we find the shortest path from each water cell to its nearest land, and we want the maximum of these shortest paths.
common_pitfalls:
- title: Single-Source BFS from Each Land Cell
description: |
A naive approach might run BFS from each land cell separately and track distances to all water cells. This results in **O(n^4)** complexity for an `n x n` grid — with `n = 100`, that's 100 million operations.
Multi-source BFS runs once from all land cells together, giving us **O(n^2)** time, which is optimal since we must visit each cell at least once.
wrong_approach: "Run separate BFS from each land cell"
correct_approach: "Start BFS from all land cells simultaneously"
- title: Forgetting Edge Cases
description: |
The problem requires returning `-1` in two scenarios:
- The grid contains only land (no water cells to measure distance to)
- The grid contains only water (no land cells to measure distance from)
Check these cases before starting BFS by counting land cells. If the count is `0` or equals `n * n`, return `-1`.
wrong_approach: "Assume grid always has both land and water"
correct_approach: "Check for all-land or all-water grids first"
- title: Not Using Manhattan Distance Correctly
description: |
BFS on a grid naturally computes Manhattan distance when you only move in four cardinal directions (up, down, left, right). Each step adds 1 to the distance.
If you accidentally allow diagonal moves, you'd be computing Chebyshev distance instead. Stick to the four-directional movement for this problem.
wrong_approach: "Include diagonal neighbours in BFS"
correct_approach: "Only explore up, down, left, right neighbours"
key_takeaways:
- "**Multi-source BFS**: When you need the shortest distance from any of multiple sources, start BFS from all sources at once"
- "**Flip the perspective**: Instead of 'from each water, find nearest land', think 'from all land, expand to all water' — this changes O(n^4) to O(n^2)"
- "**Grid BFS pattern**: Use a queue, track visited cells (by modifying the grid or using a set), and explore four-directionally"
- "**Related problems**: This pattern appears in problems like 'Walls and Gates', 'Rotting Oranges', and '01 Matrix'"
time_complexity: "O(n^2). Each cell in the `n x n` grid is visited at most once during BFS."
space_complexity: "O(n^2). In the worst case, the queue can hold all cells (when land is only at one corner)."
solutions:
- approach_name: Multi-Source BFS
is_optimal: true
code: |
from collections import deque
def max_distance(grid: list[list[int]]) -> int:
n = len(grid)
queue = deque()
# Add all land cells to the queue as starting points
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
queue.append((i, j, 0)) # (row, col, distance)
# Edge case: no land or no water
if len(queue) == 0 or len(queue) == n * n:
return -1
# Four directions: up, down, left, right
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
max_dist = -1
while queue:
row, col, dist = queue.popleft()
for dr, dc in directions:
new_row, new_col = row + dr, col + dc
# Check bounds and if it's unvisited water
if 0 <= new_row < n and 0 <= new_col < n and grid[new_row][new_col] == 0:
# Mark as visited by setting to non-zero
grid[new_row][new_col] = dist + 1
max_dist = max(max_dist, dist + 1)
queue.append((new_row, new_col, dist + 1))
return max_dist
explanation: |
**Time Complexity:** O(n^2) — We visit each cell at most once.
**Space Complexity:** O(n^2) — The queue can hold up to n^2 cells in the worst case.
We start BFS from all land cells simultaneously. Each water cell gets marked with its distance to the nearest land. The maximum distance encountered is our answer. We modify the grid in-place to track visited cells.
- approach_name: Dynamic Programming
is_optimal: false
code: |
def max_distance(grid: list[list[int]]) -> int:
n = len(grid)
INF = n * 2 # Maximum possible Manhattan distance + 1
# dist[i][j] = distance to nearest land
dist = [[0 if grid[i][j] == 1 else INF for j in range(n)] for i in range(n)]
# First pass: top-left to bottom-right
# Consider paths coming from top or left
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
continue # Land cells have distance 0
if i > 0:
dist[i][j] = min(dist[i][j], dist[i-1][j] + 1)
if j > 0:
dist[i][j] = min(dist[i][j], dist[i][j-1] + 1)
# Second pass: bottom-right to top-left
# Consider paths coming from bottom or right
for i in range(n - 1, -1, -1):
for j in range(n - 1, -1, -1):
if grid[i][j] == 1:
continue
if i < n - 1:
dist[i][j] = min(dist[i][j], dist[i+1][j] + 1)
if j < n - 1:
dist[i][j] = min(dist[i][j], dist[i][j+1] + 1)
# Find maximum distance among water cells
max_dist = -1
for i in range(n):
for j in range(n):
if grid[i][j] == 0:
max_dist = max(max_dist, dist[i][j])
# If no water or distance is still INF (no land), return -1
return max_dist if max_dist != INF and max_dist != -1 else -1
explanation: |
**Time Complexity:** O(n^2) — Two passes through the grid.
**Space Complexity:** O(n^2) — We store a distance matrix.
This DP approach computes the distance to the nearest land using two passes. The first pass propagates distances from top-left, the second from bottom-right. Together, they cover all four directions. While equally efficient as BFS, this approach is slightly less intuitive but avoids queue overhead.