You are given an n x n
integer matrix grid
where each value grid[i][j]
represents the elevation at that point (i, j)
. The rain starts to fall. At time t
, the depth of the water everywhere is t
. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t
. You can swim infinite distances in zero time. Of course, you must stay within the boundaries of the grid during your swim.
Return the least time until you can reach the bottom right square (n - 1, n - 1)
if you start at the top left square (0, 0)
.
For example, consider the following grid:
[[0,2],
[1,3]]
At time 0, you are in grid location (0, 0). You cannot go anywhere else because 4-directionally adjacent neighbors have a higher elevation than t = 0. You cannot reach point (1, 1) until time 3. When the depth of water is 3, we can swim anywhere inside the grid.
Another example:
[[0,1,2,3,4],
[24,23,22,21,5],
[12,13,14,15,16],
[11,17,18,19,20],
[10,9,8,7,6]]
The output should be 16 because we need to wait until time 16 so that (0, 0) and (4, 4) are connected.
## Data Structures and Algorithms
### Problem
You are given an `n x n` integer matrix `grid` where each value `grid[i][j]` represents the elevation at that point `(i, j)`. The rain starts to fall. At time `t`, the depth of the water everywhere is `t`. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most `t`. You can swim infinite distances in zero time. Of course, you must stay within the boundaries of the grid during your swim. Return the least time until you can reach the bottom right square `(n - 1, n - 1)` if you start at the top left square `(0, 0)`.
### Brute Force Solution
A brute-force solution would involve trying all possible times `t` from 0 to `n*n - 1`. For each time `t`, we can perform a Depth-First Search (DFS) or Breadth-First Search (BFS) starting from the top-left cell `(0, 0)` to see if we can reach the bottom-right cell `(n - 1, n - 1)` while only visiting cells with elevation at most `t`. If we find a `t` for which this is possible, we return `t`. The time complexity of this approach is high, since we are re-exploring the grid for potentially every single time `t`.
```python
def swim_in_rising_water_brute_force(grid):
n = len(grid)
def can_reach_destination(time):
visited = set()
def dfs(row, col):
if (row, col) == (n - 1, n - 1):
return True
visited.add((row, col))
# Possible moves: up, down, left, right
moves = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dr, dc in moves:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < n and 0 <= new_col < n and \
(new_row, new_col) not in visited and \
grid[new_row][new_col] <= time:
if dfs(new_row, new_col):
return True
return False
if grid[0][0] > time:
return False
return dfs(0, 0)
for time in range(n * n):
if can_reach_destination(time):
return time
We can optimize the brute-force approach by using binary search to find the minimum time t
. The idea is to perform binary search on the range of possible times [max(grid[0][0], grid[n-1][n-1]), n*n - 1]
. For each time t
in the binary search, we use DFS or BFS to check if we can reach the destination. If we can reach the destination, we try a lower time. Otherwise, we try a higher time. This reduces the time complexity significantly.
def swim_in_rising_water(grid):
n = len(grid)
def can_reach_destination(time):
visited = set()
def dfs(row, col):
if (row, col) == (n - 1, n - 1):
return True
visited.add((row, col))
moves = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dr, dc in moves:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < n and 0 <= new_col < n and \
(new_row, new_col) not in visited and \
grid[new_row][new_col] <= time:
if dfs(new_row, new_col):
return True
return False
if grid[0][0] > time:
return False
return dfs(0, 0)
left, right = max(grid[0][0], grid[n-1][n-1]), n * n - 1
ans = right
while left <= right:
mid = (left + right) // 2
if can_reach_destination(mid):
ans = mid
right = mid - 1
else:
left = mid + 1
return ans
Another optimal solution involves using Dijkstra's algorithm. Dijkstra's algorithm is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge weights, producing a shortest-path tree. We can treat the grid as a graph where each cell is a node, and the edges connect adjacent cells. The weight of an edge between two cells is the maximum elevation of the two cells. We want to find the path from the top-left cell to the bottom-right cell with the minimum maximum edge weight. This is known as the min-max path problem. Dijkstra's algorithm can find it for us.
import heapq
def swim_in_rising_water_dijkstra(grid):
n = len(grid)
pq = [(grid[0][0], 0, 0)] # (elevation, row, col)
visited = set()
ans = 0
while pq:
elevation, row, col = heapq.heappop(pq)
ans = max(ans, elevation)
if (row, col) == (n - 1, n - 1):
return ans
visited.add((row, col))
moves = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dr, dc in moves:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < n and 0 <= new_col < n and (new_row, new_col) not in visited:
heapq.heappush(pq, (grid[new_row][new_col], new_row, new_col))
can_reach_destination
function using DFS takes O(N^2) time in the worst case. The outer loop iterates n*n
times, so the total time complexity is O(N^4). The DFS has to explore all the cells.can_reach_destination
function using DFS takes O(N^2) time. Binary search iterates O(log(N^2)) = O(log N) times. Thus, the total time complexity is O(N^2 log N).visited
set in DFS takes O(N^2) space in the worst case.visited
set in DFS takes O(N^2) space.visited
set takes O(N^2) space, and the heap can contain at most all N^2 cells, so the heap also uses O(N^2) space. Total space is O(N^2).grid[0][0]
).