Path With Minimum Effort

MediumGraphs · Dijkstra (Minimax) · Binary Search🧩 Shortest PathsLeetCode #1631 ↗Updated

Problem

A hiker stands on the top-left cell of a rows × cols grid heights and wants to reach the bottom-right cell, moving up, down, left or right. The effort of a route is the largest absolute height difference between two consecutive cells on it. Return the smallest possible effort of any route.
Inputheights=[[1,2,2],[3,8,2],[5,3,5]]Output2
Inputheights=[[1,2,3],[3,8,4],[5,3,5]]Output1
Inputheights=[[4]]Output0

1 ≤ rows, cols ≤ 100  ·  1 ≤ heights[i][j] ≤ 106

Try it yourself

Java
class Solution {
    public int minimumEffortPath(int[][] heights) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
The cost of a route is a max, not a sum — but extending a route can still only keep or raise its cost. That monotonicity is all Dijkstra needs: relax with max(effort[u], |h(u) − h(v)|) and pop the smallest effort first.
PatternShortest Paths

Instinct Weighted edges plus "minimum cost, time or effort to reach" - grab a min-heap and always expand the cheapest unsettled node next (Dijkstra).

Memory hack Pop the cheapest, skip the stale, relax the rest - Dijkstra is a ripple in a pond that touches near shores before far ones.

Open the Shortest Paths guide →

Approaches

Brute ForceRepeated Relaxation Until Stable

Time O((R·C)2)Space O(R·C)

Keep effort[cell], initially infinite except the start. Sweep the whole grid repeatedly, improving each neighbour with max(effort[cell], step), until a sweep changes nothing. Like Bellman-Ford, a best route has at most R·C cells, so the number of sweeps is bounded.

Java
public int minimumEffortPath(int[][] heights) {
    int rows = heights.length, cols = heights[0].length;
    int[][] effort = new int[rows][cols];
    for (int[] row : effort) Arrays.fill(row, Integer.MAX_VALUE);
    effort[0][0] = 0;
    int[][] dirs = { {1, 0}, {-1, 0}, {0, 1}, {0, -1} };
    boolean changed = true;
    while (changed) {
        changed = false;
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                if (effort[r][c] == Integer.MAX_VALUE) continue;
                for (int[] d : dirs) {
                    int nr = r + d[0], nc = c + d[1];
                    if (nr < 0 || nc < 0 || nr >= rows || nc >= cols) continue;
                    int step = Math.abs(heights[nr][nc] - heights[r][c]);
                    int cand = Math.max(effort[r][c], step);
                    if (cand < effort[nr][nc]) {
                        effort[nr][nc] = cand;
                        changed = true;
                    }
                }
            }
        }
    }
    return effort[rows - 1][cols - 1];
}

Obviously correct, but may need a full grid sweep per cell of path length.

At a glance

LevelApproachTimeSpace
Brute ForceRepeated Relaxation Until StableO((R·C)2)O(R·C)
BetterBinary Search on the Answer + BFSO(R·C · log H)O(R·C)
OptimalDijkstra on Max-Edge CostO(R·C · log(R·C))O(R·C)
Common pitfalls
  • Summing the differences — effort is the maximum single step, not the total.
  • Plain BFS/DFS without a priority — the first visit to a cell is not its best effort.
  • Forgetting the 1×1 grid — the answer is 0 with no steps at all.
Interview tips
  • Name the variant: “minimax path” — Dijkstra with max instead of +.
  • Offer binary search + BFS as an easier-to-code fallback and explain the monotonicity.
  • Mention the Kruskal / Union-Find alternative to show breadth.
Pattern
Minimax shortest path → minimise the worst edge on a route: Dijkstra with max, or binary search + BFS.
My notes saved in this browser