Loading problems...
TL;DR: The optimal solution involves running Dijkstra's algorithm three times to find a convergence node that minimizes the sum of distances from src1 to , src2 to , and to .
destIn the LeetCode 2203 problem, "Minimum Weighted Subgraph With the Required Paths," you are given a weighted directed graph and three specific nodes: src1, src2, and dest. The goal is to identify a subgraph (a subset of edges) with the minimum total weight such that paths exist from both src1 to dest and src2 to dest.
Essentially, we are looking for the cheapest way to route two separate starting points to a single destination, allowing the paths to merge at any intermediate node to share the cost of the remaining journey.
A naive brute force approach might attempt to iterate through all possible subgraphs to check for connectivity, but the number of subgraphs is exponential (), making this impossible.
A more structured but still inefficient approach is to iterate through every possible "meeting point" node in the graph. For every candidate node , we would calculate:
src1 to .src2 to .dest.While calculating the shortest path from a single source is efficient, repeating this process naively for every possible intersection node is problematic.
Pseudo-code for Naive Approach:
textmin_weight = infinity for each node i from 0 to n-1: dist1 = run_dijkstra(src1, target=i) dist2 = run_dijkstra(src2, target=i) dist3 = run_dijkstra(i, target=dest) if all reachable: min_weight = min(min_weight, dist1 + dist2 + dist3)
Complexity Analysis: Running Dijkstra's algorithm takes . If we run this specifically for every candidate node (specifically the third step, calculating ), we might end up running a shortest path algorithm times. This results in a time complexity of approximately . Given the constraints , this approach will result in a Time Limit Exceeded (TLE) error.
The key intuition is recognizing the geometric structure of the optimal solution. The paths from src1 and src2 to dest will likely look like a "Y" shape.
src1 to some intersection node .src2 to the same intersection node .dest.Note that could be src1, src2, dest, or any other node in the graph. If the paths never merge until the very end, is simply dest.
To solve this efficiently, we need the shortest distance from src1 to all nodes, from src2 to all nodes, and from all nodes to dest.
dist[src1][i] for all requires one Dijkstra run.dist[src2][i] for all requires one Dijkstra run.dist[i][dest] for all is the tricky part. Standard Dijkstra computes "one-to-many" distances. To compute "many-to-one" (from all nodes to dest), we can reverse the graph.Graph Reversal Insight: If we reverse the direction of every edge in the graph to create , the shortest path from to in has the same weight as the shortest path from to in the original graph.
This allows us to precompute all three required distance components using exactly three runs of Dijkstra's algorithm, reducing the complexity significantly compared to the brute force method.
Visual Description:
Imagine the graph as a network of roads. We flood the network from src1 to record when the "water" reaches every node. We do the same from src2. Finally, we reverse the flow of the rivers (reverse graph) and flood from dest backwards. For any node , the time it takes for water to arrive from src1, src2, and the "reverse flow" from dest represents the total weight of the subgraph meeting at .

Graph Construction: Build the adjacency list for the original graph . Simultaneously, build the adjacency list for the reversed graph , where an edge with weight in becomes with weight in .
Distance Arrays: Initialize three arrays to store minimum distances, filling them with infinity:
dist_a: Stores shortest distances from src1 to all nodes using .dist_b: Stores shortest distances from src2 to all nodes using .dist_dest: Stores shortest distances from dest to all nodes using .Run Dijkstra:
src1 on to populate dist_a.src2 on to populate dist_b.dest on to populate .Find Minimum Weight: Iterate through every node from to . Calculate the potential subgraph weight as dist_a[i] + dist_b[i] + dist_dest[i]. The answer is the minimum of these sums.
Edge Cases: If the minimum sum remains infinity (meaning no such node exists that connects all three points), return -1.
Initialization:
adj and rev_adj lists.edges input.min_weight to infinity (Long.MAX_VALUE).Dijkstra Execution 1:
(0, src1).src1, update neighbors in dist_a.src1.Dijkstra Execution 2:
(0, src2).src2, update neighbors in dist_b.src2.Dijkstra Execution 3 (Reverse):
(0, dest).rev_adj.dest (stored in dist_dest).Aggregation:
i from 0 to n-1.dist_a[i], dist_b[i], and dist_dest[i] are all reachable (not infinity).dist_a[i] + dist_b[i] + dist_dest[i].min_weight = min(min_weight, current sum).Result:
min_weight is still infinity, return -1.min_weight.The correctness relies on the optimal substructure property of shortest paths. Any valid subgraph that connects src1 to dest and src2 to dest consists of a path and . These two paths must share a set of edges (possibly empty) ending at dest.
There must be a first node where the two paths merge and remain merged until dest. Before , the paths are disjoint (or share nodes without sharing the subsequent path segment). To minimize the total weight, the path from to dest must be the shortest path between them. Similarly, the paths from src1 to and src2 to must be shortest paths.
Since our algorithm iterates through all possible nodes as the potential merge point and uses Dijkstra to find the mathematically minimal cost to reach/leave , we are guaranteed to find the global minimum.
cpp#include <vector> #include <queue> #include <algorithm> using namespace std; class Solution { public: // Helper function to run Dijkstra vector<long long> dijkstra(int n, const vector<vector<pair<int, int>>>& adj, int src) { vector<long long> dist(n, -1); // -1 represents infinity/unreachable priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq; dist[src] = 0; pq.push({0, src}); while (!pq.empty()) { long long d = pq.top().first; int u = pq.top().second; pq.pop(); if (d > dist[u] && dist[u] != -1) continue; for (auto& edge : adj[u]) { int v = edge.first; int weight = edge.second; if (dist[v] == -1 || dist[u] + weight < dist[v]) { dist[v] = dist[u] + weight; pq.push({dist[v], v}); } } } return dist; } long long minimumWeight(int n, vector<vector<int>>& edges, int src1, int src2, int dest) { // Build adjacency list and reversed adjacency list vector<vector<pair<int, int>>> adj(n); vector<vector<pair<int, int>>> rev_adj(n); for (const auto& edge : edges) { adj[edge[0]].push_back({edge[1], edge[2]}); rev_adj[edge[1]].push_back({edge[0], edge[2]}); } // Run Dijkstra 3 times vector<long long> dist1 = dijkstra(n, adj, src1); vector<long long> dist2 = dijkstra(n, adj, src2); vector<long long> distDest = dijkstra(n, rev_adj, dest); long long min_w = -1; // Iterate through all possible meeting points for (int i = 0; i < n; i++) { if (dist1[i] == -1 || dist2[i] == -1 || distDest[i] == -1) continue; long long current_w = dist1[i] + dist2[i] + distDest[i]; if (min_w == -1 || current_w < min_w) { min_w = current_w; } } return min_w; } };
javaimport java.util.*; class Solution { public long minimumWeight(int n, int[][] edges, int src1, int src2, int dest) { // Build adjacency lists List<List<int[]>> adj = new ArrayList<>(); List<List<int[]>> revAdj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); revAdj.add(new ArrayList<>()); } for (int[] edge : edges) { adj.get(edge[0]).add(new int[]{edge[1], edge[2]}); revAdj.get(edge[1]).add(new int[]{edge[0], edge[2]}); } // Run Dijkstra 3 times long[] dist1 = dijkstra(n, adj, src1); long[] dist2 = dijkstra(n, adj, src2); long[] distDest = dijkstra(n, revAdj, dest); long minWeight = Long.MAX_VALUE; // Check all convergence points for (int i = 0; i < n; i++) { if (dist1[i] == Long.MAX_VALUE || dist2[i] == Long.MAX_VALUE || distDest[i] == Long.MAX_VALUE) { continue; } long currentWeight = dist1[i] + dist2[i] + distDest[i]; minWeight = Math.min(minWeight, currentWeight); } return minWeight == Long.MAX_VALUE ? -1 : minWeight; } private long[] dijkstra(int n, List<List<int[]>> graph, int src) { long[] dist = new long[n]; Arrays.fill(dist, Long.MAX_VALUE); dist[src] = 0; // Min-heap stores {distance, node} PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0])); pq.offer(new long[]{0, src}); while (!pq.isEmpty()) { long[] curr = pq.poll(); long d = curr[0]; int u = (int) curr[1]; if (d > dist[u]) continue; for (int[] edge : graph.get(u)) { int v = edge[0]; int weight = edge[1]; if (dist[u] + weight < dist[v]) { dist[v] = dist[u] + weight; pq.offer(new long[]{dist[v], v}); } } } return dist; } }
pythonimport heapq import sys class Solution: def minimumWeight(self, n: int, edges: List[List[int]], src1: int, src2: int, dest: int) -> int: # Build adjacency lists adj = [[] for _ in range(n)] rev_adj = [[] for _ in range(n)] for u, v, w in edges: adj[u].append((v, w)) rev_adj[v].append((u, w)) # Dijkstra implementation def dijkstra(graph, start_node): dists = [float('inf')] * n dists[start_node] = 0 pq = [(0, start_node)] while pq: d, u = heapq.heappop(pq) if d > dists[u]: continue for v, weight in graph[u]: if dists[u] + weight < dists[v]: dists[v] = dists[u] + weight heapq.heappush(pq, (dists[v], v)) return dists # Run Dijkstra 3 times dist1 = dijkstra(adj, src1) dist2 = dijkstra(adj, src2) dist_dest = dijkstra(rev_adj, dest) min_w = float('inf') # Iterate through all possible meeting nodes for i in range(n): if dist1[i] == float('inf') or dist2[i] == float('inf') or dist_dest[i] == float('inf'): continue min_w = min(min_w, dist1[i] + dist2[i] + dist_dest[i]) return min_w if min_w != float('inf') else -1
Time Complexity:
Space Complexity:
The Dijkstra pattern used here is fundamental for weighted graph problems. You will find similar logic in:
In all these cases, the core strategy involves a priority queue to explore the most promising paths first, enforcing the optimal substructure property.
Why does the naive BFS/Dijkstra approach TLE?
dist_destWhy do I get Wrong Answer on large test cases?
long or long long for distance calculations.Why do I need to reverse the graph?
dest from all other nodes is equivalent to finding the shortest path from dest in the reversed graph. Without this, you cannot efficiently calculate the dist(i, dest) component for all i.Why isn't the meeting point always on the shortest path between src1 and src2?
src1 and src2 are very far from each other but both are close to a third node which is close to dest. The optimal subgraph is about minimizing the sum of three segments, not just connecting src1 to src2.