28 · Union-Find & Minimum Spanning Trees
#1489 Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree
frequently reported atMeta
the problem
In plain words
Given a connected weighted undirected graph, return two lists of edge indices: critical edges (removing it makes every MST heavier) and pseudo-critical edges (in some MST but not all).
in n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
out [[0,1],[2,3,4,5]]
Edges 0 and 1 appear in every MST; 2–5 appear in some.
in n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
out [[],[0,1,2,3]]
Any 3 of the 4 equal edges form an MST.
- 2 ≤ n ≤ 100
- 1 ≤ edges.length ≤ min(200, n(n−1)/2)
- 1 ≤ weight ≤ 1000
- The graph is connected
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
LeetCode-style: complete the class. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternMinimum spanning tree (Kruskal / Prim)Union-Find (DSU)
Compute the MST weight W once. An edge is critical if Kruskal without it gives more than W (or can't connect). Otherwise it is pseudo-critical if Kruskal that starts with it still achieves W.
solutions
From brute force to optimal
For each edge run two full Kruskals (skip it / force it), each re-sorting the edges.
- W = MST weight.
- For each edge i: skip → weight > W? critical.
- Else force → weight == W? pseudo-critical.
2E Kruskal runs, each dominated by an O(E log E) sort.
One DSU and one index list per run.
class Solution { public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { int best = mst(n, edges, -1, -1); List<Integer> critical = new ArrayList<>(), pseudo = new ArrayList<>(); for (int i = 0; i < edges.length; i++) { if (mst(n, edges, i, -1) > best) critical.add(i); // removing it hurts else if (mst(n, edges, -1, i) == best) pseudo.add(i); // forcing it is free } return List.of(critical, pseudo); } /** Kruskal from scratch every call: copy, sort, union. Disconnected → MAX_VALUE. */ private int mst(int n, int[][] edges, int skip, int force) { int[] parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; int weight = 0, used = 0; if (force != -1) { parent[find(parent, edges[force][0])] = find(parent, edges[force][1]); weight += edges[force][2]; used++; } List<Integer> order = new ArrayList<>(); for (int i = 0; i < edges.length; i++) order.add(i); order.sort(Comparator.comparingInt(i -> edges[i][2])); // re-sorted on every call for (int i : order) { if (i == skip || i == force) continue; int ra = find(parent, edges[i][0]), rb = find(parent, edges[i][1]); if (ra == rb) continue; parent[ra] = rb; weight += edges[i][2]; used++; } return used == n - 1 ? weight : Integer.MAX_VALUE; } private int find(int[] parent, int x) { while (parent[x] != x) x = parent[x] = parent[parent[x]]; return x; }}Sort edge indices by weight once and reuse that order for all 2E + 1 Kruskal runs.
- Sort indices by weight; W = Kruskal().
- Critical: Kruskal(skip = i) > W (disconnected counts as ∞).
- Pseudo-critical: not critical and Kruskal(force = i) == W.
One sort, then 2E + 1 linear passes of near-constant unions. With E ≤ 200 that is tiny.
The sorted index array plus a fresh DSU per run.
class Solution { private int[][] edges; private Integer[] order; // edge indices sorted by weight, computed once public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) { this.edges = edges; order = new Integer[edges.length]; for (int i = 0; i < edges.length; i++) order[i] = i; Arrays.sort(order, Comparator.comparingInt(i -> edges[i][2])); int best = mst(n, -1, -1); List<Integer> critical = new ArrayList<>(), pseudo = new ArrayList<>(); for (int i = 0; i < edges.length; i++) { if (mst(n, i, -1) > best) critical.add(i); else if (mst(n, -1, i) == best) pseudo.add(i); } return List.of(critical, pseudo); } /** Kruskal that can skip one edge or start with one edge forced in. */ private int mst(int n, int skip, int force) { int[] parent = new int[n], size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } int weight = 0, used = 0; if (force != -1) { union(parent, size, edges[force][0], edges[force][1]); weight += edges[force][2]; used++; } for (int i : order) { if (i == skip || i == force) continue; if (union(parent, size, edges[i][0], edges[i][1])) { weight += edges[i][2]; if (++used == n - 1) break; } } return used == n - 1 ? weight : Integer.MAX_VALUE; } private int find(int[] parent, int x) { if (parent[x] != x) parent[x] = find(parent, parent[x]); return parent[x]; } private boolean union(int[] parent, int[] size, int a, int b) { int ra = find(parent, a), rb = find(parent, b); if (ra == rb) return false; if (size[ra] < size[rb]) { int t = ra; ra = rb; rb = t; } parent[rb] = ra; size[ra] += size[rb]; return true; }}in the room
How to say it in the interview
talk track
- Define both terms with the skip/force tests before coding — that's the whole solution.
- Explain why "force then Kruskal" works: Kruskal is optimal given any starting forest that can be extended, so if the forced edge is in some MST the weight stays W.
traps
- Not treating "graph disconnected after skipping" as heavier (it must count as critical).
- Testing pseudo-critical for edges already found critical.
what they ask next
Follow-ups
Faster than O(E²)?
Group edges by weight; within each group, edges that join different current components are candidates, and the bridges among them (Tarjan's bridge-finding on the compressed graph) are critical. O(E log E) overall — mention, don't code.
before you submit
Edge cases to test
- A single edge between two nodes → critical.
- The graph is already a tree → every edge is critical.
- All weights equal on a cycle → no critical edges, every edge pseudo-critical.
- An edge heavier than every alternative is in no MST → in neither list.
practise next
Similar problems
- #1584Min Cost to Connect All PointsMediumin Union-Find & MST
- #684Redundant ConnectionMediumin Union-Find & MST