You are given an undirected, connected graph with n
nodes labeled from 0
to n-1
and an array edges
where edges[i] = [fromi, toi, weighti]
represents an edge between nodes fromi
and toi
with a certain weight.
Your task is to find all critical and pseudo-critical edges in any minimum spanning tree (MST) of the graph:
Constraints:
edges.length
== m, 1 ≤ m ≤ min(200, n * (n - 1) / 2)(fromi, toi)
are unique.The problem is about identifying which edges must appear in every minimum spanning tree (critical), and which edges can appear in some but not all minimum spanning trees (pseudo-critical).
The brute-force way would be to enumerate all possible MSTs and see which edges appear in all or some of them. However, this is computationally infeasible for larger graphs due to the combinatorial explosion.
Instead, we can use properties of Kruskal's algorithm and MSTs:
We use Kruskal's algorithm and the Union-Find (Disjoint Set Union) data structure for efficient MST construction and cycle detection.
The Union-Find data structure is used to efficiently track connected components and avoid cycles during MST construction.
Example Input:
n = 5
edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
edges = [[0,1,1,0],[1,2,1,1],[2,3,2,2],[0,3,2,3],[0,4,3,4],[3,4,3,5],[1,4,6,6]]
[[0,1],[2,3,4,5]]
Brute-force: Enumerating all MSTs would be exponential in the number of edges, which is infeasible for large graphs.
Optimized Approach:
This problem leverages MST properties and Kruskal's algorithm to efficiently classify edges as critical or pseudo-critical. By simulating MST construction with and without each edge, we avoid brute-force enumeration and use the Union-Find structure for cycle detection and component tracking. The approach is systematic, efficient, and highlights the connection between MST algorithms and edge importance in network design.
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0]*n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
xr, yr = self.find(x), self.find(y)
if xr == yr:
return False
if self.rank[xr] < self.rank[yr]:
self.parent[xr] = yr
else:
self.parent[yr] = xr
if self.rank[xr] == self.rank[yr]:
self.rank[xr] += 1
return True
class Solution:
def findCriticalAndPseudoCriticalEdges(self, n, edges):
edges = [edge + [i] for i, edge in enumerate(edges)]
edges.sort(key=lambda x: x[2])
def kruskal(skip=None, force=None):
uf = UnionFind(n)
weight = 0
count = 0
if force is not None:
u, v, w, i = edges[force]
if uf.union(u, v):
weight += w
count += 1
for idx, (u, v, w, i) in enumerate(edges):
if idx == skip:
continue
if uf.union(u, v):
weight += w
count += 1
return weight if count == n-1 else float('inf')
minWeight = kruskal()
critical, pseudo = [], []
for i in range(len(edges)):
# Check critical
if kruskal(skip=i) > minWeight:
critical.append(edges[i][3])
elif kruskal(force=i) == minWeight:
pseudo.append(edges[i][3])
return [critical, pseudo]
class UnionFind {
public:
vector<int> parent, rank;
UnionFind(int n): parent(n), rank(n, 0) {
for (int i = 0; i < n; ++i) parent[i] = i;
}
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
bool unite(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) return false;
if (rank[xr] < rank[yr]) parent[xr] = yr;
else {
parent[yr] = xr;
if (rank[xr] == rank[yr]) rank[xr]++;
}
return true;
}
};
class Solution {
public:
vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
int m = edges.size();
vector<vector<int>> newEdges;
for (int i = 0; i < m; ++i) {
auto e = edges[i];
e.push_back(i);
newEdges.push_back(e);
}
sort(newEdges.begin(), newEdges.end(), [](const vector<int>& a, const vector<int>& b) {
return a[2] < b[2];
});
auto kruskal = [&](int skip, int force) {
UnionFind uf(n);
int weight = 0, count = 0;
if (force != -1) {
auto& e = newEdges[force];
if (uf.unite(e[0], e[1])) {
weight += e[2];
count++;
}
}
for (int i = 0; i < m; ++i) {
if (i == skip) continue;
auto& e = newEdges[i];
if (uf.unite(e[0], e[1])) {
weight += e[2];
count++;
}
}
return count == n-1 ? weight : INT_MAX;
};
int minWeight = kruskal(-1, -1);
vector<int> critical, pseudo;
for (int i = 0; i < m; ++i) {
if (kruskal(i, -1) > minWeight)
critical.push_back(newEdges[i][3]);
else if (kruskal(-1, i) == minWeight)
pseudo.push_back(newEdges[i][3]);
}
return {critical, pseudo};
}
};
class UnionFind {
int[] parent, rank;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; ++i) parent[i] = i;
}
public int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
public boolean union(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) return false;
if (rank[xr] < rank[yr]) parent[xr] = yr;
else {
parent[yr] = xr;
if (rank[xr] == rank[yr]) rank[xr]++;
}
return true;
}
}
class Solution {
public List<List<Integer>> findCriticalAndPseudoCriticalEdges(int n, int[][] edges) {
int m = edges.length;
int[][] newEdges = new int[m][4];
for (int i = 0; i < m; ++i) {
newEdges[i][0] = edges[i][0];
newEdges[i][1] = edges[i][1];
newEdges[i][2] = edges[i][2];
newEdges[i][3] = i;
}
Arrays.sort(newEdges, (a, b) -> Integer.compare(a[2], b[2]));
int minWeight = kruskal(n, newEdges, -1, -1);
List<Integer> critical = new ArrayList<>();
List<Integer> pseudo = new ArrayList<>();
for (int i = 0; i < m; ++i) {
if (kruskal(n, newEdges, i, -1) > minWeight)
critical.add(newEdges[i][3]);
else if (kruskal(n, newEdges, -1, i) == minWeight)
pseudo.add(newEdges[i][3]);
}
List<List<Integer>> res = new ArrayList<>();
res.add(critical);
res.add(pseudo);
return res;
}
private int kruskal(int n, int[][] edges, int skip, int force) {
UnionFind uf = new UnionFind(n);
int weight = 0, count = 0;
if (force != -1) {
int[] e = edges[force];
if (uf.union(e[0], e[1])) {
weight += e[2];
count++;
}
}
for (int i = 0; i < edges.length; ++i) {
if (i == skip) continue;
int[] e = edges[i];
if (uf.union(e[0], e[1])) {
weight += e[2];
count++;
}
}
return count == n-1 ? weight : Integer.MAX_VALUE;
}
}
class UnionFind {
constructor(n) {
this.parent = Array.from({length: n}, (_, i) => i);
this.rank = new Array(n).fill(0);
}
find(x) {
if (this.parent[x] !== x)
this.parent[x] = this.find(this.parent[x]);
return this.parent[x];
}
union(x, y) {
let xr = this.find(x), yr = this.find(y);
if (xr === yr) return false;
if (this.rank[xr] < this.rank[yr]) this.parent[xr] = yr;
else {
this.parent[yr] = xr;
if (this.rank[xr] === this.rank[yr]) this.rank[xr]++;
}
return true;
}
}
var findCriticalAndPseudoCriticalEdges = function(n, edges) {
let newEdges = edges.map((e, i) => [...e, i]);
newEdges.sort((a, b) => a[2] - b[2]);
function kruskal(skip, force) {
let uf = new UnionFind(n);
let weight = 0, count = 0;
if (force !== -1) {
let [u, v, w, idx] = newEdges[force];
if (uf.union(u, v)) {
weight += w;
count++;
}
}
for (let i = 0; i < newEdges.length; ++i) {
if (i === skip) continue;
let [u, v, w, idx] = newEdges[i];
if (uf.union(u, v)) {
weight += w;
count++;
}
}
return count === n - 1 ? weight : Infinity;
}
let minWeight = kruskal(-1, -1);
let critical = [], pseudo = [];
for (let i = 0; i < newEdges.length; ++i) {
if (kruskal(i, -1) > minWeight)
critical.push(newEdges[i][3]);
else if (kruskal(-1, i) === minWeight)
pseudo.push(newEdges[i][3]);
}
return [critical, pseudo];
};