-1

I am solving this question on LeetCode.com called Path With Minimum Effort:

You are given heights, a 2D array of size rows x columns, where heights[row][col] represents the height of cell (row, col). Aim is to go from top left to bottom right. 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. For e.g., if heights = [[1,2,2],[3,8,2],[5,3,5]], the answer is 2 (in green).

enter image description here

The code I have is:

class Solution {
public:
    vector<pair<int,int>> getNeighbors(vector<vector<int>>& h, int r, int c) {
        vector<pair<int,int>> n;
        if(r+1<h.size()) n.push_back({r+1,c});
        if(c+1<h[0].size()) n.push_back({r,c+1});
        if(r-1>=0) n.push_back({r-1,c});
        if(c-1>=0) n.push_back({r,c-1});
        return n;
    }
    
    int minimumEffortPath(vector<vector<int>>& heights) {
        int rows=heights.size(), cols=heights[0].size();
        
        using arr=array<int, 3>;
        priority_queue<arr, vector<arr>, greater<arr>> pq;
        vector<vector<int>> dist(rows, vector<int>(cols, INT_MAX));
        pq.push({0,0,0});    //r,c,weight
        dist[0][0]=0;
        
        //Dijkstra
        while(pq.size()) {
            auto [r,c,wt]=pq.top();
            pq.pop();
            
            if(wt>dist[r][c]) continue;
            
            vector<pair<int,int>> neighbors=getNeighbors(heights, r, c);
            for(auto n: neighbors) {
                int u=n.first, v=n.second;
                int curr_cost=abs(heights[u][v]-heights[r][c]);
                if(dist[u][v]>max(curr_cost,wt)) {
                    dist[u][v]=max(curr_cost,wt);
                    pq.push({u,v,dist[u][v]});
                }
            }
        }
        
        return dist[rows-1][cols-1];
    }
};

This gets accepted, but I have two questions:

a. Since we update dist[u][v] if it is greater than max(curr_cost,wt), how does it guarantee that in the end we return the minimum effort required? That is, why don't we end up returning the effort of the one in red above?

b. Some solutions such as this one, short-circuit and return immediately when we reach the bottom right the first time (ie, if(r==rows-1 and c==cols-1) return wt;) - how does this work? Can't we possibly get a shorter dist when we revisit the bottom right node in future?

Someone
  • 611
  • 4
  • 13
  • So you wrote code that you don't understand? – ChrisMM Oct 10 '21 at 00:07
  • @ChrisMM, with many WA for figuring out the `dist[u][v]>max(curr_cost,wt)` condition, yes. – Someone Oct 10 '21 at 00:09
  • @Someone Wow, that's what I'm calling _efficient learning_. Advice: Better waste your time with some [good books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – πάντα ῥεῖ Oct 10 '21 at 00:23
  • Dijkstra's has two closely related concepts: **visited** and **explored**. A node is **visited** when any incoming edge is used to arrive at the node. A node is **explored** when its outgoing edges are used to visit its neighbors. The key design feature of Dijkstra's is that after a node has been **explored**, additional **visits** to that node will never improve the distance to that node. That's the reason for the priority queue. The priority queue guarantees that the node being **explored** has the smallest distance of any unexplored nodes. – user3386109 Oct 10 '21 at 00:27
  • @user3386109, thank you - that makes me realize I should've pushed {dist, u, v} into the priority_queue - not {u, v, dist} like I am doing right now. Also, if you could advise me on (a), that would be very helpful. My hunch is that Dijkstra finds the "shortest" path and in this case shortest _is_ the minimum value of bottom right; but I am not sure. Thanks again! – Someone Oct 10 '21 at 00:32
  • `dist[u][v]` is the distance (i.e. the worst absolute difference) along the path from the start node to the node at coordinates `(u,v)`. When the node is revisited (before being explored) its distance may be improved, i.e. the new path being used to arrive at that node may have a lower worst-absolute-difference. So (a) just updates the node with the lower distance. – user3386109 Oct 10 '21 at 00:48
  • @user3386109, yes, I get that. But my confusion stems from the `**max**(curr_cost,wt)` condition. Given that we update `dist[u][v]` with this **max** value, how do we know that the value would be **minimal** when we reach bottom right? – Someone Oct 10 '21 at 00:51
  • The max is required by the fact that when you move from {r,c} to {u,v}, the "distance" from the start to {u,v} is **either** the same as the distance from the start to {r,c}, **or** its the difference in height between {r,c} and {u,v}, **whichever is greater**. That's just the definition of the "distance". You can't arrive at {u,v} via {r,c} without paying that cost. – user3386109 Oct 10 '21 at 01:05
  • @user3386109, yes, my question is, at bottom right, why do we get 2 (from the green path) and not 3(from the red path)? I think it is from the definition of Dijkstra's algorithm, right? It finds the **shortest** path from the source to destination - in this case, **shortest** path is equivalent to the **minimal** value asked in the question. Is my understanding correct? Thank you for your response. – Someone Oct 10 '21 at 01:10
  • 1
    You *will* get 3 from the red path first, because the red path will come out of the queue first (it has a distance of 1 until the last move). So the move from the last 2 to the 5 will set `dist[2][2] = 3`. But when the algorithm will explores the 3 at (row=2,col=1) on the green path, you have `dist[2][2] = 3`, `curr_cost=2`, and `wt=2`. So `dist[2][2] > max(curr_cost, wt)`, and `dist[2][2]` gets reduced to 2. So yes, the shortest path is the path with the **minimum** maximum-absolute-difference. – user3386109 Oct 10 '21 at 01:20
  • 1
    @user3386109, perfect! That answers my question. If you convert it into an answer, please let me know. I will be more than happy to accept it. Thanks again! :) – Someone Oct 10 '21 at 01:23

1 Answers1

0

The problem statement requires that we find the path with the minimum "effort".
And "effort" is defined as the maximum difference in heights between adjacent cells on a path.

The expression max(curr_cost, wt) takes care of the maximum part of the problem statement. When moving from one cell to another, the distance to the new cell is either the same as the distance to the old cell, or it's the difference in heights, whichever is greater. Hence max(difference_in_heights, distance_to_old_cell).

And Dijkstra's algorithm takes care of the minimum part of the problem statement, where instead of using a distance from the start node, we're using the "effort" needed to get from the start node to any given node. Dijkstra's attempts to minimize the distance, and hence it minimizes the effort.

Dijkstra's has two closely related concepts: visited and explored. A node is visited when any incoming edge is used to arrive at the node. A node is explored when its outgoing edges are used to visit its neighbors. The key design feature of Dijkstra's is that after a node has been explored, additional visits to that node will never improve the distance to that node. That's the reason for the priority queue. The priority queue guarantees that the node being explored has the smallest distance of any unexplored nodes.

In the sample grid, the red path will be explored before the green path because the red path has effort 1 until the last move, whereas the green path has effort 2. So the red path will set the distance to the bottom right cell to 3, i.e. dist[2][2] = 3.

But when the green path is explored, and we arrive at the 3 at row=2, col=1, we have

  • dist[2][2] = 3
  • curr_cost=2
  • wt=2

So dist[2][2] > max(curr_cost, wt), and dist[2][2] gets reduced to 2.

The answers to the questions:

a. The red path does set the bottom right cell to a distance of 3, temporarily. But the result of the red path is discarded in favor of the result from the green path. This is the natural result of Dijkstra's algorithm searching for the minimum.

b. When the bottom right node is ready to be explored, i.e. it's at the head of the priority queue, then it has the best distance it will ever have, so the algorithm can stop at that point. This is also a natural result of Dijkstra's algorithm. The priority queue guarantees that after a node has been explored, no later visit to that node will reduce its distance.

user3386109
  • 34,287
  • 7
  • 49
  • 68