DEV Community

Cover image for 🛤️ Beginner-Friendly Guide 'Minimum Cost Path with Edge Reversals' - LeetCode 3650 (C++, Python, JavaScript)
Om Shree
Om Shree

Posted on

🛤️ Beginner-Friendly Guide 'Minimum Cost Path with Edge Reversals' - LeetCode 3650 (C++, Python, JavaScript)

Navigating a graph is usually a one-way street, but what if you could briefly flip the direction of traffic to reach your destination? This problem challenges us to find the most efficient route when we have the special ability to reverse edges at a specific cost. It is a fantastic way to learn how to adapt classic shortest-path algorithms to handle unconventional rules.


You're given:

  • A directed graph with nodes and a list of weighted edges.
  • A special "switch" at each node that allows you to reverse one incoming edge once you arrive there.
  • A cost rule: traversing a normal edge costs , while traversing a reversed edge costs .

Your goal:

Find the minimum total cost to travel from node 0 to node . If the destination is unreachable, return -1.

Example:

Example 1:

Input: n = 4, edges = [[0,1,3],[3,1,1],[2,3,4],[0,2,2]]

Output: 5

Explanation:

Image description : Example 1

Use the path 0 → 1 (cost 3).
At node 1 reverse the original edge 3 → 1 into 1 → 3 and traverse it at cost 2 * 1 = 2.
Total cost is 3 + 2 = 5.

Example 2:

Input: n = 4, edges = [[0,2,1],[2,1,1],[1,3,1],[2,3,3]]

Output: 3

Explanation:

No reversal is needed. Take the path 0 → 2 (cost 1), then 2 → 1 (cost 1), then 1 → 3 (cost 1).
Total cost is 1 + 1 + 1 = 3.


Intuition: The "Ghost" Edge Strategy

At first glance, the rule about "reversing an edge only once upon arrival" sounds like we need to keep track of a lot of state. however, because we can only use the reversal immediately to move to the next node, we can simplify our thinking.

Essentially, every original directed edge with weight provides two possibilities:

  1. Move from to normally for cost .
  2. If we are at , we can use the switch to turn the edge into for cost .

By adding these "reverse options" as additional edges into our graph from the start, we transform the problem into a standard shortest-path search. Since all weights are non-negative, Dijkstra's Algorithm is the perfect tool for the job.


Walkthrough: Understanding the Examples

Example 1:

  • Nodes: 4, Edges: [[0,1,3],[3,1,1],[2,3,4],[0,2,2]]
  • Possible moves from node 0:
  • To node 1 (cost 3) or node 2 (cost 2).

  • If we go :

  • At node 1, we see an incoming edge from node 3 (3 -> 1 weight 1). We reverse it to go for cost .

  • Total cost: .

  • If we go :

  • At node 2, there are no incoming edges to reverse that help us get closer to node 3.

  • The path is the cheapest. Output: 5.


Code Blocks

C++

class Solution {
public:
    int minCost(int n, vector<vector<int>>& edges) {
        // Build adjacency list with both original and reversed edges
        vector<vector<pair<int, int>>> adj(n);
        for (const auto& edge : edges) {
            int u = edge[0], v = edge[1], w = edge[2];
            adj[u].push_back({v, w});          // Normal edge
            adj[v].push_back({u, 2 * w});      // Reversed edge option
        }

        // Dijkstra's algorithm using a priority queue
        priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
        vector<long long> dist(n, LLONG_MAX);

        dist[0] = 0;
        pq.push({0, 0});

        while (!pq.empty()) {
            long long d = pq.top().first;
            int u = pq.top().second;
            pq.pop();

            if (d > dist[u]) continue;

            for (auto& edge : adj[u]) {
                int v = edge.first;
                int weight = edge.second;
                if (dist[u] + weight < dist[v]) {
                    dist[v] = dist[u] + weight;
                    pq.push({dist[v], v});
                }
            }
        }

        return dist[n - 1] == LLONG_MAX ? -1 : (int)dist[n - 1];
    }
};

Enter fullscreen mode Exit fullscreen mode

Python

import heapq

class Solution:
    def minCost(self, n: int, edges: list[list[int]]) -> int:
        adj = [[] for _ in range(n)]
        for u, v, w in edges:
            adj[u].append((v, w))       # Normal edge
            adj[v].append((u, 2 * w))   # Reversed edge option

        # Priority queue stores (cost, current_node)
        pq = [(0, 0)]
        dist = [float('inf')] * n
        dist[0] = 0

        while pq:
            current_dist, u = heapq.heappop(pq)

            if current_dist > dist[u]:
                continue

            for v, weight in adj[u]:
                if dist[u] + weight < dist[v]:
                    dist[v] = dist[u] + weight
                    heapq.heappush(pq, (dist[v], v))

        return int(dist[n - 1]) if dist[n - 1] != float('inf') else -1

Enter fullscreen mode Exit fullscreen mode

JavaScript

/**
 * @param {number} n
 * @param {number[][]} edges
 * @return {number}
 */
var minCost = function(n, edges) {
    const adj = Array.from({ length: n }, () => []);
    for (const [u, v, w] of edges) {
        adj[u].push([v, w]);       // Normal
        adj[v].push([u, 2 * w]);   // Reversed
    }

    const dist = new Array(n).fill(Infinity);
    dist[0] = 0;

    // Using a simple Min-Priority Queue structure
    const pq = new MinPriorityQueue({ priority: x => x[0] });
    pq.enqueue([0, 0]); // [cost, node]

    while (!pq.isEmpty()) {
        const [d, u] = pq.dequeue().element;

        if (d > dist[u]) continue;

        for (const [v, weight] of adj[u]) {
            if (dist[u] + weight < dist[v]) {
                dist[v] = dist[u] + weight;
                pq.enqueue([dist[v], v]);
            }
        }
    }

    return dist[n - 1] === Infinity ? -1 : dist[n - 1];
};

Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Graph Augmentation: Sometimes, the best way to handle a "special move" is to represent it as a new type of edge in your graph.
  • Dijkstra's Efficiency: When finding the shortest path in a graph with non-negative weights, Dijkstra's algorithm provides an efficient complexity of .
  • State Simplification: By recognizing that the "reversal" is used immediately, we avoid complex DP states or tracking if a switch was used globally.

Final Thoughts

This problem is a classic example of how interviewers take a standard algorithm (Dijkstra) and add a "twist" to see if you can adapt. In real-world software engineering, this logic mirrors how routing engines work. For example, in a logistics system, a truck might usually take a highway, but under certain conditions, it might take a service road at a higher cost in fuel or time. Modeling these "conditional" paths is key to building robust navigation and optimization systems.

Top comments (0)