🧠
Dijkstra’s Algorithm Variant
💡 This approach models the problem as a graph and uses a modified Dijkstra’s algorithm to find the path minimizing the maximum edge weight rather than sum of weights.
Intuition
Use a priority queue to always explore the path with the smallest current maximum effort, updating neighbors with the minimal maximum effort found so far.
Algorithm
- Initialize a 2D array dist with infinity to track minimal max effort to each cell.
- Use a min-heap priority queue starting from (0,0) with effort 0.
- Pop the cell with smallest current effort from the queue.
- For each neighbor, calculate effort = max(current effort, edge difference).
- If effort < dist[neighbor], update dist and push neighbor into queue.
- Return dist at bottom-right cell after processing.
💡 The key difference from classic Dijkstra is that edge costs combine by max, not sum, requiring careful updates.
from typing import List
import heapq
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
rows, cols = len(heights), len(heights[0])
dist = [[float('inf')] * cols for _ in range(rows)]
dist[0][0] = 0
heap = [(0, 0, 0)] # effort, r, c
directions = [(1,0), (-1,0), (0,1), (0,-1)]
while heap:
effort, r, c = heapq.heappop(heap)
if r == rows - 1 and c == cols - 1:
return effort
if effort > dist[r][c]:
continue
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
next_effort = max(effort, abs(heights[r][c] - heights[nr][nc]))
if next_effort < dist[nr][nc]:
dist[nr][nc] = next_effort
heapq.heappush(heap, (next_effort, nr, nc))
return 0
# Example usage:
# sol = Solution()
# print(sol.minimumEffortPath([[1,2,2],[3,8,2],[5,3,5]])) # Output: 2
Line Notes
dist = [[float('inf')] * cols for _ in range(rows)]Initialize distances with infinity to find minimal max effort for each cell
heap = [(0, 0, 0)]Start from top-left cell with zero effort in min-heap
if effort > dist[r][c]Skip processing if a better path to this cell was already found
next_effort = max(effort, abs(heights[r][c] - heights[nr][nc]))Update effort as max of current path effort and edge difference to neighbor
import java.util.*;
public class Solution {
private int rows, cols;
private int[][] heights;
private int[][] directions = {{1,0},{-1,0},{0,1},{0,-1}};
public int minimumEffortPath(int[][] heights) {
this.heights = heights;
rows = heights.length;
cols = heights[0].length;
int[][] dist = new int[rows][cols];
for (int[] row : dist) Arrays.fill(row, Integer.MAX_VALUE);
dist[0][0] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
pq.offer(new int[]{0, 0, 0}); // effort, r, c
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int effort = curr[0], r = curr[1], c = curr[2];
if (r == rows - 1 && c == cols - 1) return effort;
if (effort > dist[r][c]) continue;
for (int[] dir : directions) {
int nr = r + dir[0], nc = c + dir[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
int nextEffort = Math.max(effort, Math.abs(heights[r][c] - heights[nr][nc]));
if (nextEffort < dist[nr][nc]) {
dist[nr][nc] = nextEffort;
pq.offer(new int[]{nextEffort, nr, nc});
}
}
}
}
return 0;
}
// Example main
public static void main(String[] args) {
Solution sol = new Solution();
int[][] heights = {{1,2,2},{3,8,2},{5,3,5}};
System.out.println(sol.minimumEffortPath(heights)); // Output: 2
}
}
Line Notes
int[][] dist = new int[rows][cols];Track minimal max effort to reach each cell
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));Min-heap to always pick cell with smallest current effort
if (effort > dist[r][c]) continue;Skip processing if a better path was already found
int nextEffort = Math.max(effort, Math.abs(heights[r][c] - heights[nr][nc]));Update effort as max of current path effort and edge difference
#include <bits/stdc++.h>
using namespace std;
class Solution {
int rows, cols;
vector<vector<int>> heights;
vector<pair<int,int>> directions = {{1,0},{-1,0},{0,1},{0,-1}};
public:
int minimumEffortPath(vector<vector<int>>& heights) {
this->heights = heights;
rows = heights.size();
cols = heights[0].size();
vector<vector<int>> dist(rows, vector<int>(cols, INT_MAX));
dist[0][0] = 0;
using T = tuple<int,int,int>;
priority_queue<T, vector<T>, greater<T>> pq;
pq.emplace(0, 0, 0); // effort, r, c
while (!pq.empty()) {
auto [effort, r, c] = pq.top(); pq.pop();
if (r == rows - 1 && c == cols - 1) return effort;
if (effort > dist[r][c]) continue;
for (auto& dir : directions) {
int nr = r + dir.first, nc = c + dir.second;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
int nextEffort = max(effort, abs(heights[r][c] - heights[nr][nc]));
if (nextEffort < dist[nr][nc]) {
dist[nr][nc] = nextEffort;
pq.emplace(nextEffort, nr, nc);
}
}
}
}
return 0;
}
};
// Example usage:
// int main() {
// Solution sol;
// vector<vector<int>> heights = {{1,2,2},{3,8,2},{5,3,5}};
// cout << sol.minimumEffortPath(heights) << endl; // Output: 2
// return 0;
// }
Line Notes
vector<vector<int>> dist(rows, vector<int>(cols, INT_MAX));Initialize distances with max int to find minimal max effort
priority_queue<T, vector<T>, greater<T>> pq;Min-heap priority queue to pick cell with smallest effort
if (effort > dist[r][c]) continue;Skip outdated entries where better path already found
int nextEffort = max(effort, abs(heights[r][c] - heights[nr][nc]));Update effort as max of current path effort and edge difference
var minimumEffortPath = function(heights) {
const rows = heights.length, cols = heights[0].length;
const dist = Array.from({length: rows}, () => Array(cols).fill(Infinity));
dist[0][0] = 0;
const directions = [[1,0],[-1,0],[0,1],[0,-1]];
const pq = new MinPriorityQueue({priority: x => x[0]});
pq.enqueue([0, 0, 0]); // effort, r, c
while (!pq.isEmpty()) {
const [effort, r, c] = pq.dequeue().element;
if (r === rows - 1 && c === cols - 1) return effort;
if (effort > dist[r][c]) continue;
for (const [dr, dc] of directions) {
const nr = r + dr, nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
const nextEffort = Math.max(effort, Math.abs(heights[r][c] - heights[nr][nc]));
if (nextEffort < dist[nr][nc]) {
dist[nr][nc] = nextEffort;
pq.enqueue([nextEffort, nr, nc]);
}
}
}
}
return 0;
};
// Example test
// console.log(minimumEffortPath([[1,2,2],[3,8,2],[5,3,5]])); // Output: 2
// Note: MinPriorityQueue is from '@datastructures-js/priority-queue' or similar library
Line Notes
const dist = Array.from({length: rows}, () => Array(cols).fill(Infinity));Initialize distances with infinity to track minimal max effort
const pq = new MinPriorityQueue({priority: x => x[0]});Min-heap priority queue to pick cell with smallest effort
if (effort > dist[r][c]) continue;Skip stale queue entries where better path exists
const nextEffort = Math.max(effort, Math.abs(heights[r][c] - heights[nr][nc]));Update effort as max of current path effort and edge difference
TimeO(n*m*log(n*m))
SpaceO(n*m)
Dijkstra visits each cell and edges, priority queue operations cost log(n*m)
💡 For a 100x100 grid, this is about 10,000 * log(10,000) ≈ 10,000 * 14 = 140,000 operations, efficient for interviews.
Interview Verdict: Accepted
This is the optimal and most elegant approach, demonstrating mastery of graph algorithms and priority queues.