Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1631. Path With Minimum Effort - Leetcode Solution

Code Implementation

import heapq

class Solution:
    def minimumEffortPath(self, heights):
        m, n = len(heights), len(heights[0])
        efforts = [[float('inf')] * n for _ in range(m)]
        efforts[0][0] = 0
        heap = [(0, 0, 0)]  # (effort, x, y)

        directions = [(-1,0), (1,0), (0,-1), (0,1)]

        while heap:
            effort, x, y = heapq.heappop(heap)
            if x == m - 1 and y == n - 1:
                return effort
            for dx, dy in directions:
                nx, ny = x + dx, y + dy
                if 0 <= nx < m and 0 <= ny < n:
                    next_effort = max(effort, abs(heights[x][y] - heights[nx][ny]))
                    if next_effort < efforts[nx][ny]:
                        efforts[nx][ny] = next_effort
                        heapq.heappush(heap, (next_effort, nx, ny))
#include <vector>
#include <queue>
#include <cmath>
#include <utility>
using namespace std;

class Solution {
public:
    int minimumEffortPath(vector<vector<int>>& heights) {
        int m = heights.size(), n = heights[0].size();
        vector<vector<int>> effort(m, vector<int>(n, INT_MAX));
        effort[0][0] = 0;
        priority_queue<tuple<int,int,int>, vector<tuple<int,int,int>>, greater<tuple<int,int,int>>> pq;
        pq.push({0, 0, 0});
        vector<pair<int,int>> dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!pq.empty()) {
            auto [currEff, x, y] = pq.top(); pq.pop();
            if (x == m - 1 && y == n - 1) return currEff;
            for (auto [dx, dy] : dirs) {
                int nx = x + dx, ny = y + dy;
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int nextEff = max(currEff, abs(heights[x][y] - heights[nx][ny]));
                    if (nextEff < effort[nx][ny]) {
                        effort[nx][ny] = nextEff;
                        pq.push({nextEff, nx, ny});
                    }
                }
            }
        }
        return 0;
    }
};
import java.util.*;

class Solution {
    public int minimumEffortPath(int[][] heights) {
        int m = heights.length, n = heights[0].length;
        int[][] effort = new int[m][n];
        for (int[] row : effort) Arrays.fill(row, Integer.MAX_VALUE);
        effort[0][0] = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{0, 0, 0}); // {effort, x, y}
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!pq.isEmpty()) {
            int[] curr = pq.poll();
            int currEff = curr[0], x = curr[1], y = curr[2];
            if (x == m - 1 && y == n - 1) return currEff;
            for (int[] dir : dirs) {
                int nx = x + dir[0], ny = y + dir[1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int nextEff = Math.max(currEff, Math.abs(heights[x][y] - heights[nx][ny]));
                    if (nextEff < effort[nx][ny]) {
                        effort[nx][ny] = nextEff;
                        pq.offer(new int[]{nextEff, nx, ny});
                    }
                }
            }
        }
        return 0;
    }
}
var minimumEffortPath = function(heights) {
    const m = heights.length, n = heights[0].length;
    const effort = Array.from({length: m}, () => Array(n).fill(Infinity));
    effort[0][0] = 0;
    const heap = [[0, 0, 0]]; // [effort, x, y]
    const dirs = [[-1,0],[1,0],[0,-1],[0,1]];

    while (heap.length > 0) {
        heap.sort((a,b) => a[0] - b[0]);
        const [currEff, x, y] = heap.shift();
        if (x === m-1 && y === n-1) return currEff;
        for (const [dx, dy] of dirs) {
            const nx = x + dx, ny = y + dy;
            if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                const nextEff = Math.max(currEff, Math.abs(heights[x][y] - heights[nx][ny]));
                if (nextEff < effort[nx][ny]) {
                    effort[nx][ny] = nextEff;
                    heap.push([nextEff, nx, ny]);
                }
            }
        }
    }
    return 0;
};

Problem Description

Given a 2D grid heights where each cell heights[i][j] represents the height of a location, you are to find a path from the top-left cell (0, 0) to the bottom-right cell (m-1, n-1). You can move in four directions: up, down, left, or right.

The effort of a path is defined as the maximum absolute difference in heights between two consecutive cells along the path. Your goal is to find a path that minimizes this maximum effort.

  • You must return the minimum possible effort required to travel from the start to the end.
  • All cells are valid to use; you may not skip or reuse a cell in a way that creates cycles.
  • There is always at least one valid path.
  • Input constraints guarantee the grid is not empty.

Thought Process

At first glance, this problem looks similar to finding the shortest path in a grid, but with a twist: instead of summing up distances or costs, we care about the largest "jump" (height difference) we make along any path.

A brute-force approach would be to try all possible paths and, for each, calculate the maximum height difference, keeping the minimum among them. However, this is not feasible for large grids due to the exponential number of possible paths.

We need an efficient way to find the path where the largest step is as small as possible. This is similar to the "minimax path" problem, which suggests using algorithms like Dijkstra's (for shortest path with weights), but with a modification to track the maximum edge weight (effort) along each path instead of the sum.

The key realization is that we can treat each cell as a node, and moving between adjacent cells as an edge with a weight equal to the absolute height difference. We want the path from the start to the end with the minimal possible maximum edge weight.

Solution Approach

We solve this problem using a modified version of Dijkstra's algorithm:

  1. State Representation: For each cell, keep track of the minimum effort needed to reach it.
  2. Priority Queue: Use a min-heap (priority queue) to always expand the cell with the lowest current effort (maximum jump so far).
  3. Effort Update: When moving to an adjacent cell, compute the effort as the maximum of the current path's effort and the new edge's weight (height difference).
  4. Relaxation: If this new computed effort is less than the previously recorded effort for that cell, update it and push it onto the heap.
  5. Termination: When we reach the bottom-right cell, the current effort is the minimum possible.

This approach ensures we always process the most promising (lowest effort so far) cell next, just like Dijkstra's ensures the shortest path.

We use a 2D array to track the minimum effort to each cell. The heap is initialized with the starting cell at effort 0.

Example Walkthrough

Input:
heights = [ [1,2,2], [3,8,2], [5,3,5] ]

Let's trace the algorithm:

  1. Start at (0,0) with effort 0.
  2. From (0,0), can go to (0,1) [diff=1] or (1,0) [diff=2]. Both are pushed to the heap with their efforts.
  3. Pop (0,1) (lower effort). From here, can go to (0,2) [diff=0] or (1,1) [diff=6]. The effort to (0,2) is max(1,0)=1; to (1,1) is max(1,6)=6.
  4. Continue popping the lowest effort cell from the heap, updating neighbors as we go.
  5. Eventually, reach (2,2) with a minimum effort of 2, corresponding to the path (0,0)-(0,1)-(0,2)-(1,2)-(2,2), where the largest jump is 2.

The algorithm ensures we never miss a lower-effort path because we always expand the lowest-effort option first.

Time and Space Complexity

  • Brute-force: The number of possible paths from start to end is exponential, leading to O(2(m*n)) time, which is infeasible for large grids.
  • Optimized (Dijkstra-based):
    • Each cell is pushed into the priority queue at most once per unique effort value, but since we only update when we find a better path, the number of operations is O(m * n * log(m * n)), where m and n are the grid dimensions.
    • Space complexity is O(m * n) for the effort tracking array and the heap.

Summary

The "Path With Minimum Effort" problem is a twist on the classic shortest path problem, requiring us to minimize the maximum "jump" (height difference) along any path from the top-left to the bottom-right of a grid. By modeling the grid as a graph and using a modified Dijkstra's algorithm, we efficiently find the path with the smallest possible maximum effort. The approach leverages priority queues and careful bookkeeping, resulting in a solution that is both efficient and elegant.