You are given heights, a 2D array of size rows x columns, where heights[row][col] represents the height of cell (row, col). You are situated in the top-left cell, (0, 0), and you hope to travel to the bottom-right cell, (rows-1, columns-1) (i.e., 0-indexed). You can move up, down, left, or right, and you wish to find a route that requires the minimum effort.
A route's effort is the maximum absolute difference in heights between two consecutive cells of the route.
Return the minimum effort required to travel from the top-left cell to the bottom-right cell.
Example 1:
Input: heights = [
[1,1,1],
[3,2,4],
[2,5,4]
]
Output: 2Explanation: The route of [1,1,2,4,4] has a maximum absolute difference of 2 in consecutive cells.
Example 2:
Input: heights = [
[1,1,1],
[1,1,2],
[6,5,2]
]
Output: 1Explanation: The route of [1,1,1,1,1,2,2] has a maximum absolute difference of 1 in consecutive cells.
Constraints:
row == heights.lengthcol == heights[i].length1 <= row, col <= 1001 <= heights[i][j].length <= 1,000,000 .Before attempting this problem, you should be comfortable with:
The effort of a path is defined as the maximum absolute difference between consecutive cells along that path. We want to minimize this maximum difference. Dijkstra's algorithm fits well because we can treat the "effort so far" as the cost and always expand the path with the smallest maximum effort. When we reach the destination, we have found the path with minimum effort.
(0, 0, 0) representing (effort, row, col) starting at the top-left cell.dist[r][c] stores the minimum effort to reach cell (r, c). Initialize all values to infinity except dist[0][0] = 0.class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
ROWS, COLS = len(heights), len(heights[0])
minHeap = [[0, 0, 0]] # [diff, row, col]
visit = set()
directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
while minHeap:
diff, r, c = heapq.heappop(minHeap)
if (r, c) in visit:
continue
visit.add((r, c))
if (r, c) == (ROWS - 1, COLS - 1):
return diff
for dr, dc in directions:
newR, newC = r + dr, c + dc
if (
newR < 0 or newC < 0 or
newR >= ROWS or newC >= COLS or
(newR, newC) in visit
):
continue
newDiff = max(diff, abs(heights[r][c] - heights[newR][newC]))
heapq.heappush(minHeap, [newDiff, newR, newC])
return 0Where is the number of rows and is the number of columns in the given matrix.
We can binary search on the answer. For a given effort limit, we check whether it is possible to reach the destination using only edges with absolute differences at most that limit. dfs explores if a valid path exists under the constraint. If a path exists, we try a smaller limit; otherwise, we try a larger one.
0 and 1,000,000 (the maximum possible difference).mid), run a dfs from (0, 0) to check if we can reach (ROWS-1, COLS-1) using only edges with absolute difference at most mid.dfs, mark cells as visited and only move to neighbors where the height difference is within the limit.dfs reaches the destination, the limit is feasible. Record it and search for a smaller limit.dfs fails, search for a larger limit.class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
ROWS, COLS = len(heights), len(heights[0])
directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
def dfs(r, c, limit, visited):
if r == ROWS - 1 and c == COLS - 1:
return True
visited.add((r, c))
for dr, dc in directions:
newR, newC = r + dr, c + dc
if (newR < 0 or newC < 0 or
newR >= ROWS or newC >= COLS or
(newR, newC) in visited or
abs(heights[newR][newC] - heights[r][c]) > limit):
continue
if dfs(newR, newC, limit, visited):
return True
return False
l, r = 0, 1000000
res = r
while l <= r:
mid = (l + r) // 2
if dfs(0, 0, mid, set()):
res = mid
r = mid - 1
else:
l = mid + 1
return resWhere is the number of rows and is the number of columns in the given matrix.
We can view the grid as a graph where each cell is a node and edges connect adjacent cells with weights equal to the absolute height difference. The problem becomes finding the path from top-left to bottom-right that minimizes the maximum edge weight. Kruskal's algorithm processes edges in sorted order, and we stop as soon as the source and destination become connected. The last edge added determines the minimum effort.
(weight, cell1, cell2). Map each cell to a unique index.0) and bottom-right cell are connected.0.class DSU:
def __init__(self, n):
self.Parent = list(range(n + 1))
self.Size = [1] * (n + 1)
def find(self, node):
if self.Parent[node] != node:
self.Parent[node] = self.find(self.Parent[node])
return self.Parent[node]
def union(self, u, v):
pu = self.find(u)
pv = self.find(v)
if pu == pv:
return False
if self.Size[pu] < self.Size[pv]:
pu, pv = pv, pu
self.Size[pu] += self.Size[pv]
self.Parent[pv] = pu
return True
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
ROWS, COLS = len(heights), len(heights[0])
edges = []
for r in range(ROWS):
for c in range(COLS):
if r + 1 < ROWS:
edges.append((abs(heights[r][c] - heights[r + 1][c]), r * COLS + c, (r + 1) * COLS + c))
if c + 1 < COLS:
edges.append((abs(heights[r][c] - heights[r][c + 1]), r * COLS + c, r * COLS + c + 1))
edges.sort()
dsu = DSU(ROWS * COLS)
for weight, u, v in edges:
dsu.union(u, v)
if dsu.find(0) == dsu.find(ROWS * COLS - 1):
return weight
return 0Where is the number of rows and is the number of columns in the given matrix.
SPFA can also solve this problem by treating effort as the distance metric. We use a queue to process cells and update their minimum effort when a better path is found. Unlike Dijkstra's, SPFA does not require a priority queue, trading off guaranteed optimal ordering for simplicity and potential efficiency on some inputs.
dist array with infinity for all cells except dist[0] = 0 for the starting cell.max(current effort, height difference).dist for the bottom-right cell.class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
ROWS, COLS = len(heights), len(heights[0])
dist = [float('inf')] * (ROWS * COLS)
dist[0] = 0
in_queue = [False] * (ROWS * COLS)
def index(r, c):
return r * COLS + c
queue = deque([0])
in_queue[0] = True
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
while queue:
u = queue.popleft()
in_queue[u] = False
r, c = divmod(u, COLS)
for dr, dc in directions:
newR, newC = r + dr, c + dc
if 0 <= newR < ROWS and 0 <= newC < COLS:
v = index(newR, newC)
weight = abs(heights[r][c] - heights[newR][newC])
new_dist = max(dist[u], weight)
if new_dist < dist[v]:
dist[v] = new_dist
if not in_queue[v]:
queue.append(v)
in_queue[v] = True
return dist[ROWS * COLS - 1]Where is the number of rows and is the number of columns in the given matrix.
The effort of a path is defined as the maximum absolute difference along the path, not the sum. Using addition instead of max() when computing the new effort will give incorrect results. Always compute newEffort = max(currentEffort, abs(height[current] - height[neighbor])).
When the grid has only one cell, the answer is 0 since we are already at the destination. Some implementations may fail to handle this edge case, especially when the algorithm expects at least one move. Check for ROWS == 1 && COLS == 1 as a base case or ensure your algorithm naturally returns 0.
In Dijkstra's algorithm, all distances should be initialized to infinity except for the starting cell which should be 0. Some implementations mistakenly initialize all distances to zero or forget to set the starting distance, causing the algorithm to skip valid relaxations or produce incorrect minimum efforts.