3807. Minimum Cost to Repair Edges to Traverse a Graph ๐
Problem Description
You are given an undirected graph with n nodes labeled from 0 to n - 1. The graph consists of m edges represented by a 2D integer array edges, where edges[i] = [uแตข, vแตข, wแตข] indicates that there is an edge between nodes uแตข and vแตข with a repair cost of wแตข.
You are also given an integer k. Initially, all edges are damaged, meaning none of them can be used until they are repaired.
You may choose a non-negative integer money and repair all edges whose repair cost is less than or equal to money. Every edge whose repair cost exceeds money remains damaged and cannot be used.
Your goal is to travel from node 0 to node n - 1 using at most k edges along the path.
Return an integer denoting the minimum amount of money required to make this possible. If there is no choice of money that allows you to reach node n - 1 from node 0 within k edges, return -1.
In short, you need to find the smallest budget such that, after repairing all edges costing no more than that budget, a path from node 0 to node n - 1 exists that uses no more than k edges.
How We Pick the Algorithm
Why BFS?
This problem maps to BFS through a short path in the full flowchart.
The shortest sequence of transitions maps to BFS in the state-space graph.
Open in FlowchartShow step-by-step reasoning
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves
nnodes and a set of edges connecting them, which is a clear graph structure.
Is it a tree?
- No: The graph is a general undirected graph that may contain cycles and multiple edges, not a tree.
Is the problem related to directed acyclic graphs?
- No: The graph is undirected, so it is not a directed acyclic graph.
Is the problem related to shortest paths?
- No: We are not minimizing path length or path cost directly; instead, we want the minimum budget so that a path within
kedges exists. The traversal itself only needs to check reachability under an edge-count limit.
Does the problem involve connectivity?
- No: While reachability matters, the core question is not simply whether two nodes are connected via a union-find style grouping; it depends on the number of edges used in the path.
Does the problem have small constraints?
- No: The constraints are not tiny enough to justify exhaustive DFS/backtracking over all paths.
BFS:
- Yes: For a fixed budget, BFS naturally explores nodes layer by layer, where each layer corresponds to one additional edge used. This lets us check whether node
n - 1is reachable from node0withinkedges. Combined with binary search over the sorted repair costs, BFS verifies feasibility for each candidate budget.
Conclusion: The flowchart guides us toward using the Breadth-First Search (BFS) pattern (paired with Binary Search over edge costs) to determine the minimum money required to reach node n - 1 from node 0 using at most k edges.
Intuition
The key observation is about the relationship between the chosen budget money and the edges we can use. As money increases, more edges get repaired and become available. More available edges can only help us reach node n - 1 โ they never make it harder. This means the problem has a monotonic property:
- If a budget
moneyallows us to travel from node0to noden - 1using at mostkedges, then any larger budget also works (since the larger budget keeps all those repaired edges and possibly adds more). - If a budget is too small to succeed, then any smaller budget also fails.
Whenever we see this "works for everything above a threshold, fails for everything below it" structure, it's a strong signal to use binary search on the answer. We're searching for the smallest budget that flips the result from "impossible" to "possible".
Next, we notice that the optimal money doesn't need to be an arbitrary number. Repairing an edge only matters at the exact cost of some edge โ increasing money between two consecutive edge costs changes nothing. So the minimum valid budget must be one of the values in edges. This lets us sort the edges by repair cost and binary search over their indices rather than over a numeric range. If we pick index mid, we repair all edges with cost less than or equal to edges[mid][2].
Finally, for each candidate budget during the binary search, we must verify the actual condition: can we go from node 0 to node n - 1 using at most k edges? Since "at most k edges" is about the number of edges in the path (every edge counts the same regardless of its repair cost), this is an unweighted reachability-within-k-steps question. BFS is the perfect fit here: it explores the graph layer by layer, where each new layer represents using one more edge. If we reach node n - 1 at a layer (distance) that is <= k, the budget is feasible.
Putting it together: sort the edges by cost, binary search on the index to find the smallest budget, and at each step run a BFS over the currently repaired edges to check whether node n - 1 is reachable within k edges. If even repairing all edges fails, the answer is -1.
Pattern Learn more about Breadth-First Search, Graph and Binary Search patterns.
Solution Approach
We use Binary Search + BFS to solve this problem efficiently.
Step 1: Sort the edges by repair cost.
Since the minimum valid budget must equal one of the edge costs, we first sort edges by their cost w:
edges.sort(key=lambda x: x[2])
After sorting, choosing index mid means our budget is edges[mid][2], and all edges from index 0 to mid (which all have cost <= edges[mid][2]) are repaired and usable.
Step 2: Binary search over the edge index.
We binary search on the index of the answer, with left boundary l = 0 and right boundary r = m - 1, where m = len(edges). For each middle index mid = (l + r) >> 1, we check whether repairing edges edges[0..mid] lets us reach node n - 1 from node 0 within k edges:
- If
check(mid)returnsTrue, the budget is enough, so we shrink the search to the left half by settingr = mid. - Otherwise, we need a bigger budget, so we set
l = mid + 1.
l, r = 0, m - 1 while l < r: mid = (l + r) >> 1 if check(mid): r = mid else: l = mid + 1
This loop converges on the smallest index whose budget might work.
Step 3: The check function using BFS.
For a given index idx, we build an adjacency list g using only the edges edges[0..idx] (the repaired ones):
g = [[] for _ in range(n)]
for u, v, _ in edges[: idx + 1]:
g[u].append(v)
g[v].append(u)
Then we run a layer-by-layer BFS starting from node 0. We track the current layer with dist, where dist represents the number of edges used so far. A vis array prevents revisiting nodes. At each layer:
- If we encounter node
n - 1, we return whetherdist <= k(we reached the target within the allowed edge count). - Otherwise, we expand all unvisited neighbors into the next layer
nq, and incrementdist.
q = [0] dist = 0 vis = [False] * n vis[0] = True while q: nq = [] for u in q: if u == n - 1: return dist <= k for v in g[u]: if not vis[v]: vis[v] = True nq.append(v) q = nq dist += 1 return False
Because BFS visits nodes in increasing order of distance, the first time we reach node n - 1 gives the minimum number of edges needed, which is exactly what we compare against k.
Step 4: Final verification and answer.
After the binary search ends, l points to the candidate index. We run check(l) one more time to confirm it actually satisfies the requirement (this handles the case where even repairing all edges is insufficient). If it succeeds, we return edges[l][2]; otherwise, we return -1:
return edges[l][2] if check(l) else -1
Complexity Analysis.
- Time complexity:
O((n + m) ร log m). The binary search runsO(log m)iterations, and eachcheckcall builds the graph and runs a BFS, costingO(n + m). The initial sort costsO(m ร log m). - Space complexity:
O(n + m)for the adjacency list, the visited array, and the BFS queue.
Example Walkthrough
Let's trace through a concrete example to see how Binary Search + BFS works.
Input:
n = 4(nodes labeled0, 1, 2, 3)edges = [[0, 1, 4], [0, 2, 1], [1, 3, 5], [2, 3, 2]]k = 2(path may use at most 2 edges)
We want the smallest budget money so that, after repairing all edges with cost <= money, we can travel from node 0 to node 3 using at most 2 edges.
Step 1: Sort the edges by repair cost.
Before: [[0,1,4], [0,2,1], [1,3,5], [2,3,2]] After: [[0,2,1], [2,3,2], [0,1,4], [1,3,5]] idx 0 idx 1 idx 2 idx 3 cost=1 cost=2 cost=4 cost=5
Now m = 4, so we binary search over indices [0, 3]. Picking index mid means budget = edges[mid][2] and edges 0..mid are repaired.
Step 2 & 3: Binary search with BFS checks.
Initialize l = 0, r = 3.
Iteration 1: mid = (0 + 3) >> 1 = 1. Budget = edges[1][2] = 2.
Repaired edges: [0,2,1], [2,3,2]. Adjacency list:
0 -> [2] 2 -> [0, 3] 3 -> [2] 1 -> []
Run BFS from node 0:
- Layer
dist = 0: queue[0]. Node0โ3. Neighbors โ visit2. Next queue[2]. - Layer
dist = 1: queue[2]. Node2โ3. Neighbors โ visit3(skip0). Next queue[3]. - Layer
dist = 2: queue[3]. Node3found! Returndist <= kโ2 <= 2โ True.
check(1) = True, so the budget is enough. Shrink right: r = 1.
Iteration 2: l = 0, r = 1. mid = (0 + 1) >> 1 = 0. Budget = edges[0][2] = 1.
Repaired edges: [0,2,1] only. Adjacency list:
0 -> [2] 2 -> [0] 1 -> [] 3 -> []
Run BFS from node 0:
- Layer
dist = 0: queue[0]. Node0โ3. Visit2. Next queue[2]. - Layer
dist = 1: queue[2]. Node2โ3. Only neighbor0already visited. Next queue[]. - Queue empty โ return False (node
3unreachable).
check(0) = False, so we need a bigger budget. Move left: l = mid + 1 = 1.
Now l == r == 1, the loop ends.
Step 4: Final verification and answer.
l = 1. Run check(1) once more โ True (as computed above).
Return edges[1][2] = 2.
Why the answer is correct:
- Budget = 1 repairs only edge
0โ2. We can reach node2but there's no way onward to node3. Fails. - Budget = 2 repairs
0โ2and2โ3, giving the path0 โ 2 โ 3using exactly2edges, which satisfiesk = 2. Works.
Notice the monotonic property in action: budget 1 fails, and every budget >= 2 succeeds. The binary search efficiently pinpointed the threshold without testing budgets 4 or 5.
Final Output: 2
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def minCost(self, n: int, edges: List[List[int]], k: int) -> int:
6 def check(idx: int) -> bool:
7 # Build an undirected graph using only the first (idx + 1) edges
8 # (i.e., the edges with the smallest weights after sorting).
9 graph = [[] for _ in range(n)]
10 for u, v, _ in edges[: idx + 1]:
11 graph[u].append(v)
12 graph[v].append(u)
13
14 # BFS from node 0 to find the shortest hop-distance to node n - 1.
15 queue = [0]
16 distance = 0
17 visited = [False] * n
18 visited[0] = True
19 while queue:
20 next_queue = []
21 for u in queue:
22 # Reached the target: valid only if hop count is within k.
23 if u == n - 1:
24 return distance <= k
25 for v in graph[u]:
26 if not visited[v]:
27 visited[v] = True
28 next_queue.append(v)
29 queue = next_queue
30 distance += 1
31 # Target unreachable with this set of edges.
32 return False
33
34 m = len(edges)
35 # Sort edges by weight so the index maps to a weight threshold.
36 edges.sort(key=lambda edge: edge[2])
37
38 # Binary search for the smallest edge index that makes check(idx) True.
39 left, right = 0, m - 1
40 while left < right:
41 mid = (left + right) >> 1
42 if check(mid):
43 right = mid
44 else:
45 left = mid + 1
46
47 # Return the corresponding weight if a valid threshold exists, else -1.
48 return edges[left][2] if check(left) else -1
491class Solution {
2 private int nodeCount;
3 private int[][] sortedEdges;
4 private int maxHops;
5
6 /**
7 * Binary searches over the sorted edge weights to find the smallest
8 * weight threshold such that node (n-1) is reachable from node 0
9 * within at most k BFS layers (hops), using only edges whose weight
10 * is no greater than that threshold.
11 */
12 public int minCost(int n, int[][] edges, int k) {
13 this.nodeCount = n;
14 this.sortedEdges = edges;
15 this.maxHops = k;
16
17 // Sort edges by ascending weight so that index in the array
18 // corresponds to a weight threshold.
19 Arrays.sort(edges, (a, b) -> a[2] - b[2]);
20
21 // Binary search on the edge index (i.e., on the weight threshold).
22 int left = 0;
23 int right = edges.length - 1;
24 while (left < right) {
25 int mid = (left + right) >> 1;
26 if (check(mid)) {
27 // Reachable using edges[0..mid]; try a smaller threshold.
28 right = mid;
29 } else {
30 // Not reachable; need a larger threshold.
31 left = mid + 1;
32 }
33 }
34
35 // Verify the final candidate; return its weight if it works.
36 return check(left) ? edges[left][2] : -1;
37 }
38
39 /**
40 * Builds a graph using only edges with index in [0, idx]
41 * (i.e., the idx+1 smallest-weight edges) and performs a
42 * layered BFS from node 0. Returns true if node (n-1) is
43 * reachable within at most maxHops layers.
44 */
45 private boolean check(int idx) {
46 // Adjacency list for the current subgraph.
47 List<Integer>[] graph = new List[nodeCount];
48 Arrays.setAll(graph, i -> new ArrayList<>());
49
50 // Add the idx+1 cheapest edges as undirected connections.
51 for (int i = 0; i <= idx; ++i) {
52 int u = sortedEdges[i][0];
53 int v = sortedEdges[i][1];
54 graph[u].add(v);
55 graph[v].add(u);
56 }
57
58 // Layered BFS: each layer increment counts one extra hop.
59 List<Integer> currentLayer = new ArrayList<>();
60 currentLayer.add(0);
61
62 int distance = 0;
63 boolean[] visited = new boolean[nodeCount];
64 visited[0] = true;
65
66 while (!currentLayer.isEmpty()) {
67 List<Integer> nextLayer = new ArrayList<>();
68 for (int u : currentLayer) {
69 // Target reached: succeed only if within the hop budget.
70 if (u == nodeCount - 1) {
71 return distance <= maxHops;
72 }
73 // Expand to unvisited neighbors for the next layer.
74 for (int v : graph[u]) {
75 if (!visited[v]) {
76 visited[v] = true;
77 nextLayer.add(v);
78 }
79 }
80 }
81 currentLayer = nextLayer;
82 ++distance;
83 }
84
85 // Target node never reached within the subgraph.
86 return false;
87 }
88}
891class Solution {
2public:
3 int minCost(int n, vector<vector<int>>& edges, int k) {
4 // Sort edges in ascending order by their weight (edge[2]).
5 // This lets us binary search on the edge index: a larger index
6 // means more edges (with greater weights) are available.
7 sort(edges.begin(), edges.end(),
8 [](const vector<int>& a, const vector<int>& b) {
9 return a[2] < b[2];
10 });
11
12 // check(idx): determine whether, using only the first (idx + 1)
13 // edges (i.e. edges with weight <= edges[idx][2]), there exists a
14 // path from node 0 to node (n - 1) that uses at most k edges (hops).
15 auto check = [&](int idx) -> bool {
16 // Build the adjacency list with the currently allowed edges.
17 vector<vector<int>> graph(n);
18 for (int i = 0; i <= idx; ++i) {
19 int u = edges[i][0];
20 int v = edges[i][1];
21 graph[u].push_back(v);
22 graph[v].push_back(u);
23 }
24
25 // BFS layer by layer from node 0; "dist" tracks the number of
26 // hops from the source to the current frontier.
27 vector<int> currentLevel;
28 currentLevel.push_back(0);
29
30 vector<char> visited(n, 0);
31 visited[0] = 1;
32
33 int dist = 0;
34 while (!currentLevel.empty()) {
35 vector<int> nextLevel;
36 for (int u : currentLevel) {
37 // Reached the destination: valid only if within k hops.
38 if (u == n - 1) {
39 return dist <= k;
40 }
41 // Expand to all unvisited neighbors.
42 for (int v : graph[u]) {
43 if (!visited[v]) {
44 visited[v] = 1;
45 nextLevel.push_back(v);
46 }
47 }
48 }
49 // Move to the next BFS layer and increment the hop count.
50 currentLevel.swap(nextLevel);
51 ++dist;
52 }
53 // Destination is unreachable with the allowed edges.
54 return false;
55 };
56
57 int m = edges.size();
58 // Binary search for the smallest edge index that makes check() true.
59 int left = 0, right = m - 1;
60 while (left < right) {
61 int mid = (left + right) >> 1;
62 if (check(mid)) {
63 right = mid; // mid works, try to find a smaller index.
64 } else {
65 left = mid + 1; // mid fails, search the larger half.
66 }
67 }
68 // If the final candidate index is valid, return its edge weight as
69 // the minimum cost; otherwise no valid path exists, return -1.
70 return check(left) ? edges[left][2] : -1;
71 }
72};
731/**
2 * Finds the minimum possible maximum edge weight along a path from node 0 to node n-1,
3 * such that node n-1 is reachable within at most k hops (BFS levels)
4 * using only edges whose weight is <= that maximum.
5 *
6 * Strategy: sort edges by weight, then binary search on the edge index.
7 * For each candidate index, build a graph using only edges [0..index]
8 * and verify reachability within k levels via BFS.
9 *
10 * @param n Number of nodes (labeled 0 .. n-1).
11 * @param edges Array of [u, v, weight] triples.
12 * @param k Maximum allowed number of hops on the path.
13 * @returns The minimum valid maximum edge weight, or -1 if unreachable.
14 */
15function minCost(n: number, edges: number[][], k: number): number {
16 // Sort edges in ascending order of weight so that index thresholds
17 // correspond to monotonically increasing maximum allowed weight.
18 edges.sort((a, b) => a[2] - b[2]);
19
20 /**
21 * Checks whether node n-1 is reachable from node 0 within k hops,
22 * using only the first (index + 1) lightest edges.
23 *
24 * @param index The highest edge index (inclusive) allowed to be used.
25 * @returns True if n-1 is reachable in <= k BFS levels, otherwise false.
26 */
27 const check = (index: number): boolean => {
28 // Build an undirected adjacency list from edges[0..index].
29 const graph: number[][] = Array.from({ length: n }, () => []);
30 for (let i = 0; i <= index; i++) {
31 const [u, v] = edges[i];
32 graph[u].push(v);
33 graph[v].push(u);
34 }
35
36 // Standard level-by-level BFS starting from node 0.
37 let queue: number[] = [0];
38 const visited: boolean[] = Array(n).fill(false);
39 visited[0] = true;
40
41 // distance tracks the current BFS level (number of hops from node 0).
42 let distance = 0;
43 while (queue.length > 0) {
44 const nextQueue: number[] = [];
45 for (const u of queue) {
46 // If the target node is reached, succeed only if within k hops.
47 if (u === n - 1) {
48 return distance <= k;
49 }
50 // Expand to all unvisited neighbors for the next level.
51 for (const v of graph[u]) {
52 if (!visited[v]) {
53 visited[v] = true;
54 nextQueue.push(v);
55 }
56 }
57 }
58 queue = nextQueue;
59 distance++;
60 }
61
62 // Target node was never reached.
63 return false;
64 };
65
66 // Binary search for the smallest edge index that makes check(index) true.
67 let left = 0;
68 let right = edges.length - 1;
69 while (left < right) {
70 const mid = (left + right) >> 1;
71 if (check(mid)) {
72 // mid works, so the answer is at mid or to its left.
73 right = mid;
74 } else {
75 // mid fails, so a larger threshold is required.
76 left = mid + 1;
77 }
78 }
79
80 // Validate the final candidate; return its weight or -1 if even the
81 // full set of edges cannot satisfy the reachability constraint.
82 return check(left) ? edges[left][2] : -1;
83}
84Time and Space Complexity
-
Time Complexity:
O((m + n) ร log m). The algorithm first sorts the edges, which takesO(m ร log m). Then it performs a binary search over the edge index range[0, m - 1], requiringO(log m)iterations. In each iteration, thecheckfunction is invoked: it rebuilds the adjacency graph by traversing up toidx + 1edges (at mostm), which costsO(m), and then runs a BFS over the partial graph that visits at mostnnodes andmedges, costingO(n + m). Thus eachcheckcall isO(m + n), and the binary search contributesO((m + n) ร log m). Combining the sorting and binary search phases yields an overall time complexity ofO((m + n) ร log m), wherenandmare the number of nodes and edges, respectively. -
Space Complexity:
O(n). Although the adjacency listgstores the edges, the dominant auxiliary structures within eachcheckcall are thevisarray of sizen, and the BFS queuesqandnq, which together hold at mostnnodes. Ignoring the graph storage that is rebuilt per call (and treating it as part of the working memory bounded by edges), the analysis follows the reference and reports the space complexity asO(n), wherenis the number of nodes.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Not handling the case where n == 1 (start equals target)
When n == 1, node 0 is node n - 1. No edges are needed, so the answer should be 0. However, the code unconditionally accesses edges[left][2]:
return edges[left][2] if check(left) else -1
If edges is empty (which is common when n == 1), then m = 0, and:
left, right = 0, -1, so thewhile left < rightloop is skipped.check(0)buildsedges[:1]on an empty list โ BFS starts at node0, immediately seesu == n - 1(since0 == 0), and returns0 <= kโTrue.- Then
edges[left][2]becomesedges[0][2]โIndexErroron the empty list.
Solution: Add an early return for the trivial case before touching edges:
m = len(edges)
# Start node is already the target; no edges (and no money) needed.
if n == 1:
return 0
if m == 0:
return -1
Pitfall 2: Treating budget as continuous instead of "one of the edge costs"
A naive instinct is to binary search over the value range of money (e.g., 0 to max(w)). This wastes work and can be subtly wrong if costs are sparse. The key insight is that the optimal money is always exactly equal to some edge's cost โ any value between two consecutive costs unlocks the same set of edges as the lower one.
Solution: Binary search over the sorted edge indices (as done here), so each candidate budget edges[mid][2] corresponds to a meaningful state change in the set of usable edges. This guarantees correctness and bounds iterations to O(log m).
Pitfall 3: Using a standard "visited-on-pop" BFS that misreports the hop distance
A frequent mistake is checking u == n - 1 only when a node is dequeued one at a time, while incrementing distance per node rather than per layer. This conflates the number of nodes processed with the number of edges traversed and yields a wrong hop count.
Solution: Use layer-by-layer BFS (as in the code), where distance is incremented once per level. Because BFS expands nodes in nondecreasing hop order, the first time node n - 1 appears, distance equals the minimum number of edges on any path โ exactly what must be compared against k.
Pitfall 4: Mutating the caller's input via in-place sort
edges.sort(...) reorders the caller's list in place. If the grader or surrounding code relies on the original edge ordering afterward, this introduces a hard-to-trace side effect.
Solution: Sort a copy instead, to keep the function pure:
edges = sorted(edges, key=lambda edge: edge[2])
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich of the following problems can be solved with backtracking (select multiple)
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Want a Structured Path to Master System Design Too? Donโt Miss This!