Modify Graph Edge Weights
HardPrompt
You are given an undirected weighted connected graph containing n nodes labeled from 0 to n - 1, and an integer array edges where edges[i] = [ai, bi, wi] indicates that there is an edge between nodes ai and bi with weight wi.
Some edges have a weight of -1 (wi = -1), while others have a positive weight (wi > 0).
Your task is to modify all edges with a weight of -1 by assigning them positive integer values in the range [1, 2 * 109] so that the shortest distance between the nodes source and destination becomes equal to an integer target. If there are multiple modifications that make the shortest distance between source and destination equal to target, any of them will be considered correct.
Return an array containing all edges (even unmodified ones) in any order if it is possible to make the shortest distance from source to destination equal to target, or an empty array if it's impossible.
Note: You are not allowed to modify the weights of edges with initial positive weights.
Example 1:

Input: n = 5, edges = [[4,1,-1],[2,0,-1],[0,3,-1],[4,3,-1]], source = 0, destination = 1, target = 5
Output: [[4,1,1],[2,0,1],[0,3,3],[4,3,1]]
Explanation: The graph above shows a possible modification to the edges, making the distance from 0 to 1 equal to 5.Example 2:

Input: n = 3, edges = [[0,1,-1],[0,2,5]], source = 0, destination = 2, target = 6
Output: []
Explanation: The graph above contains the initial edges. It is not possible to make the distance from 0 to 2 equal to 6 by modifying the edge with weight -1. So, an empty array is returned.Example 3:

Input: n = 4, edges = [[1,0,4],[1,2,3],[2,3,5],[0,3,-1]], source = 0, destination = 2, target = 6
Output: [[1,0,4],[1,2,3],[2,3,5],[0,3,1]]
Explanation: The graph above shows a modified graph having the shortest distance from 0 to 2 as 6.
Constraints:
1 <= n <= 1001 <= edges.length <= n * (n - 1) / 2edges[i].length == 30 <= ai, bi < nwi = -1or1 <= wi <= 107ai != bi0 <= source, destination < nsource != destination1 <= target <= 109- The graph is connected, and there are no self-loops or repeated edges
Approaches
2 approaches with complexity analysis and trade-offs.
This approach attempts to solve the problem with a single greedy modification after an initial shortest path calculation. The idea is to find the shortest possible path by setting all modifiable edges to weight 1. If this minimum distance is already greater than the target, it's impossible. Otherwise, we calculate the deficit and add it to the weight of a single modifiable edge on the shortest path. This is a simple and fast approach, but its major drawback is its lack of correctness. By increasing the weight of one path, we might inadvertently make another path the new shortest one.
Algorithm
- Set all edge weights of
-1to their minimum possible value,1. - Run Dijkstra's algorithm from the
sourcenode to find the shortest path to thedestination. - Let the shortest distance found be
d. - If
d > target, it's impossible to reach the target, as we've already used the minimum possible weights. Return an empty array. - If
d == target, we have found a valid modification. Return theedgesarray with all-1weights replaced by1. - If
d < target, we need to increase the shortest path's length. Calculate the differencediff = target - d. - Find any shortest path from
sourcetodestination. - Traverse this path and find the first edge
(u, v)that originally had a weight of-1. - Greedily add the entire
diffto this edge's weight. Its new weight becomes1 + diff. - All other
-1edges remain with a weight of1. - Return the modified
edgesarray.
Walkthrough
The algorithm begins by being optimistic: it assumes the best-case scenario where all modifiable edges (-1) are assigned the smallest possible positive integer weight, which is 1. With this setup, it computes the shortest distance from source to destination using Dijkstra's algorithm. Let's call this distance d_min.
If d_min is greater than target, we can immediately conclude that it's impossible to achieve the target distance. Any other valid assignment would involve weights greater than or equal to 1, leading to a shortest path of at least d_min.
If d_min is exactly equal to target, we've found a solution. We can return the edges with all -1s changed to 1.
The interesting case is when d_min < target. The greedy strategy is to take the diff = target - d_min and add it to one of the modifiable edges on the shortest path found. The hope is that this path's length becomes target, and it remains the shortest path overall. However, this is not guaranteed, as another path might now be shorter.
// This is a conceptual illustration of the flawed greedy approach.// It is not a complete and correct solution.class Solution { public int[][] modifiedGraphEdges(int n, int[][] edges, int source, int destination, int target) { // Step 1: Set all -1 weights to 1 for (int[] edge : edges) { if (edge[2] == -1) { edge[2] = 1; } } // Step 2 & 3: Run Dijkstra to find shortest path d long d = dijkstra(n, edges, source, destination); // Step 4: d > target if (d > target) { return new int[0][0]; } // Step 5: d == target if (d == target) { return edges; } // Step 6, 7, 8, 9: d < target, greedy modification long diff = target - d; for (int[] edge : edges) { // This check is flawed because we don't know if this edge was on the shortest path. // A proper implementation would require path reconstruction. // Assuming we found a -1 edge on the path that was originally -1. // The logic to identify which edges were originally -1 is missing here for brevity. // Let's say we have a way to identify them. if (isOriginalMinusOne(edge)) { // Pseudocode edge[2] += diff; // After modifying one, we would break and return. return edges; } } return new int[0][0]; // Should not be reached if a -1 edge exists on path } private long dijkstra(int n, int[][] edges, int source, int destination) { // Standard Dijkstra implementation // ... returns shortest distance return -1; // Placeholder } private boolean isOriginalMinusOne(int[] edge) { // Helper to check if an edge was originally -1 return false; // Placeholder }}Complexity
Time
O(E + V log V) for a single run of Dijkstra's algorithm with a priority queue.
Space
O(V + E), where V is the number of nodes and E is the number of edges, for storing the graph and Dijkstra's data structures.
Trade-offs
Pros
Simple to understand and implement.
Very fast, requiring only one run of Dijkstra's algorithm.
Cons
This approach is fundamentally flawed and will fail for many test cases.
The greedy choice of adding the entire difference to a single edge on one shortest path does not guarantee that this path remains the shortest. Another path, unaffected by this weight change, might become the new shortest path, resulting in a final distance less than the target.
This approach refines the flawed greedy strategy by iteratively correcting the edge weights. Instead of performing a single modification and hoping for the best, it enters a loop. In each iteration, it finds the current shortest path. If this path is shorter than the target, it increases the weight of a modifiable edge on this path to make its length equal to the target. Then, it re-calculates the shortest path for the entire graph. This process repeats until the overall shortest path from source to destination equals the target. This ensures that at each step, we are addressing the path that is preventing us from meeting the target, guaranteeing a correct solution if one exists.
Algorithm
- Create a copy of the edges or a way to track which edges were originally
-1. - In the main
edgesarray, change all-1weights to1. - Run Dijkstra's algorithm from
sourceto find the shortest distancedtodestination. - If at this point
d > target, it's impossible. Return an empty array. - Enter a loop that continues as long as
d < target: a. Calculate the needed increment:diff = target - d. b. Reconstruct the current shortest path fromsourcetodestinationusing parent pointers from the last Dijkstra run. c. Traverse this path to find the first edge(u, v)that was originally-1. d. If no such edge is found on the path, it means the shortest path's length is fixed and less thantarget. It's impossible to modify it. Return an empty array. e. Increase the weight of this edge(u, v)bydiff. This makes this path's length exactlytarget. f. Run Dijkstra's algorithm again to find the new shortest distanced. The distancedwill not decrease. - Once the loop terminates,
dmust be equal totarget. Return the modifiededgesarray.
Walkthrough
This iterative algorithm provides a robust way to find a valid weight assignment. It works by repeatedly identifying and lengthening the shortest path until it meets the target.
First, all modifiable edges (weight -1) are tentatively set to 1. We compute the shortest path distance, d. If d > target, no solution exists.
If d < target, we enter a loop. The goal of each iteration is to increase the current shortest path's length. We calculate diff = target - d. We then find one of the current shortest paths and pick a modifiable edge on it. We increase this edge's weight by diff. This strategic increase ensures that this particular path now has a length of target.
However, another path might now be the shortest. So, we run Dijkstra's again to find the new shortest distance, d'. Since we only increased an edge weight, d' will be greater than or equal to d. This process continues. In each step, the shortest distance d gets closer to target. The loop terminates when d finally equals target.
If at any point the current shortest path contains no modifiable edges and its length is less than target, we are stuck and no solution is possible.
import java.util.*; class Solution { public int[][] modifiedGraphEdges(int n, int[][] edges, int source, int destination, int target) { List<int[]>[] adj = new ArrayList[n]; for (int i = 0; i < n; i++) adj[i] = new ArrayList<>(); boolean[] isNegative = new boolean[edges.length]; for (int i = 0; i < edges.length; i++) { int u = edges[i][0], v = edges[i][1], w = edges[i][2]; if (w == -1) { isNegative[i] = true; edges[i][2] = 1; } adj[u].add(new int[]{v, edges[i][2], i}); adj[v].add(new int[]{u, edges[i][2], i}); } long[] dist = new long[n]; int[] parent = new int[n]; int[] parentEdgeIndex = new int[n]; long d = runDijkstra(n, adj, source, destination, dist, parent, parentEdgeIndex); if (d > target) { return new int[0][0]; } if (d < target) { while (d < target) { int curr = destination; boolean found = false; while (curr != source) { int p = parent[curr]; int edgeIndex = parentEdgeIndex[curr]; if (isNegative[edgeIndex]) { long diff = target - d; edges[edgeIndex][2] += diff; // Update adjacency list as well for(int[] edge : adj[p]) { if(edge[2] == edgeIndex) edge[1] += diff; } for(int[] edge : adj[curr]) { if(edge[2] == edgeIndex) edge[1] += diff; } found = true; break; } curr = p; } if (!found) { // Shortest path has no -1 edges and is < target return new int[0][0]; } d = runDijkstra(n, adj, source, destination, dist, parent, parentEdgeIndex); if (d > target) { // This can happen if we add too much weight and another path becomes shorter // but the path we modified is now much longer than target. // The new shortest path could be > target. // In this specific problem logic, this implies impossibility. return new int[0][0]; } } } // Final check if d is not exactly target if (d != target) return new int[0][0]; return edges; } private long runDijkstra(int n, List<int[]>[] adj, int source, int destination, long[] dist, int[] parent, int[] parentEdgeIndex) { Arrays.fill(dist, Long.MAX_VALUE); Arrays.fill(parent, -1); Arrays.fill(parentEdgeIndex, -1); dist[source] = 0; PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[1])); pq.offer(new long[]{source, 0}); while (!pq.isEmpty()) { long[] curr = pq.poll(); int u = (int) curr[0]; long d = curr[1]; if (d > dist[u]) continue; if (u == destination) return d; for (int[] edge : adj[u]) { int v = edge[0]; int w = edge[1]; int edgeIdx = edge[2]; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; parent[v] = u; parentEdgeIndex[v] = edgeIdx; pq.offer(new long[]{v, dist[v]}); } } } return dist[destination]; }}Complexity
Time
O(C * (E + V log V)), where `C` is the number of modifiable edges, `V` is the number of nodes, and `E` is the number of edges. The `while` loop runs at most `C` times, and each iteration is dominated by Dijkstra's algorithm.
Space
O(V + E) to store the graph, parent pointers, and other data for Dijkstra's algorithm.
Trade-offs
Pros
Guaranteed to find a correct solution if one exists.
The logic is robust and handles complex graph structures correctly by re-evaluating the global shortest path at each step.
Cons
The main drawback is the performance. In the worst case, it might run Dijkstra's algorithm many times.
The number of iterations is bounded by the number of modifiable edges, which can be large.
Solutions
Solution
class Solution {private final int inf = 2000000000;public int[][] modifiedGraphEdges(int n, int[][] edges, int source, int destination, int target) { long d = dijkstra(edges, n, source, destination); if (d < target) { return new int[0][]; } boolean ok = d == target; for (var e : edges) { if (e[2] > 0) { continue; } if (ok) { e[2] = inf; continue; } e[2] = 1; d = dijkstra(edges, n, source, destination); if (d <= target) { ok = true; e[2] += target - d; } } return ok ? edges : new int[0][]; }private long dijkstra(int[][] edges, int n, int src, int dest) { int[][] g = new int[n][n]; long[] dist = new long[n]; Arrays.fill(dist, inf); dist[src] = 0; for (var f : g) { Arrays.fill(f, inf); } for (var e : edges) { int a = e[0], b = e[1], w = e[2]; if (w == -1) { continue; } g[a][b] = w; g[b][a] = w; } boolean[] vis = new boolean[n]; for (int i = 0; i < n; ++i) { int k = -1; for (int j = 0; j < n; ++j) { if (!vis[j] && (k == -1 || dist[k] > dist[j])) { k = j; } } vis[k] = true; for (int j = 0; j < n; ++j) { dist[j] = Math.min(dist[j], dist[k] + g[k][j]); } } return dist[dest]; }}using ll = long long ; const int inf = 2e9 ; class Solution { public: vector < vector < int >> modifiedGraphEdges ( int n , vector < vector < int >>& edges , int source , int destination , int target ) { ll d = dijkstra ( edges , n , source , destination ); if ( d < target ) { return {}; } bool ok = d == target ; for ( auto & e : edges ) { if ( e [ 2 ] > 0 ) { continue ; } if ( ok ) { e [ 2 ] = inf ; continue ; } e [ 2 ] = 1 ; d = dijkstra ( edges , n , source , destination ); if ( d <= target ) { ok = true ; e [ 2 ] += target - d ; } } return ok ? edges : vector < vector < int >> {}; } ll dijkstra ( vector < vector < int >>& edges , int n , int src , int dest ) { ll g [ n ][ n ]; ll dist [ n ]; bool vis [ n ]; for ( int i = 0 ; i < n ; ++ i ) { fill ( g [ i ], g [ i ] + n , inf ); dist [ i ] = inf ; vis [ i ] = false ; } dist [ src ] = 0 ; for ( auto & e : edges ) { int a = e [ 0 ], b = e [ 1 ], w = e [ 2 ]; if ( w == - 1 ) { continue ; } g [ a ][ b ] = w ; g [ b ][ a ] = w ; } for ( int i = 0 ; i < n ; ++ i ) { int k = - 1 ; for ( int j = 0 ; j < n ; ++ j ) { if ( ! vis [ j ] && ( k == - 1 || dist [ j ] < dist [ k ])) { k = j ; } } vis [ k ] = true ; for ( int j = 0 ; j < n ; ++ j ) { dist [ j ] = min ( dist [ j ], dist [ k ] + g [ k ][ j ]); } } return dist [ dest ]; } };class Solution: def modifiedGraphEdges(self, n: int, edges: List[List[int]], source: int, destination: int, target: int) -> List[List[int]]: def dijkstra(edges: List[List[int]]) -> int: g = [[inf] * n for _ in range(n)] for a, b, w in edges: if w == - 1: continue g[a][b] = g[b][a] = w dist = [inf] * n dist[source] = 0 vis = [False] * n for _ in range(n): k = - 1 for j in range(n): if not vis[j] and (k == - 1 or dist[k] > dist[j]): k = j vis[k] = True for j in range(n): dist[j] = min(dist[j], dist[k] + g[k][j]) return dist[destination] inf = 2 * 10 ** 9 d = dijkstra(edges) if d < target: return [] ok = d == target for e in edges: if e[2] > 0: continue if ok: e[2] = inf continue e[2] = 1 d = dijkstra(edges) if d <= target: ok = True e[2] += target - d return edges if ok else []Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.