There are n servers numbered from 0 to n - 1 connected by undirected server-to-server connections forming a network where connections[i] = [ai, bi] represents a connection between servers ai and bi. Any server can reach other servers directly or indirectly through the network.
A critical connection is a connection that, if removed, will make some servers unable to reach some other server.
Return all critical connections in the network in any order.
A critical connection (bridge) is an edge whose removal increases the number of connected components in the graph. Tarjan’s algorithm allows us to efficiently find all such bridges using DFS and tracking discovery and low-link times.
Build the Graph:
Construct an undirected graph from the given connections list, where each server is a node and each connection is an undirected edge.
DFS Traversal with Discovery and Low-Link Times:
Use Depth-First Search (DFS) to traverse the graph. For each node, assign a discoveryTime (the time when the node is first visited) and a lowTime (the earliest discovered node reachable from the current node, including itself and its descendants). These are tracked using arrays like disc and low (or similar names in code).
Parent Check in DFS:
When iterating over neighbors in DFS, we skip the parent node (the node we came from). This is crucial because if we allowed the parent to update the lowTime of the current node, it would always be less than or equal to the current node’s discoveryTime, causing every edge to be incorrectly marked as a bridge. By skipping the parent, we ensure only true back-edges and child-edges are considered. (See Q1)
Visited Array Optimization:
We do not need a separate visited[] array. If lowTime[i] == 0 (or disc[i] == -1 depending on initialization), then node i has not been visited yet. This saves memory and simplifies the code. (See Q2)
Timer Variable:
The timer can be a simple integer or an array with one value (e.g., [0] in Python). Using an array allows the timer to be mutable within nested functions, but a single integer works as well in most languages. (See Q3)
Identifying Bridges:
After visiting a neighbor v from node u, if low[v] > disc[u], then the edge (u, v) is a critical connection (bridge). This means that there is no back-edge from the subtree rooted at v to any ancestor of u, so removing (u, v) would disconnect the graph. (See Q5)
Preserving Input Order (Optional):
Since the graph is undirected, the order of nodes in each connection does not matter. However, if you want to preserve the order as in the input, you can use a hash set to store the original connections. When adding a bridge to the result, check if it exists in the set in the same order; if not, reverse it. This ensures the output matches the input order where possible. (See Q4)
Summary of Key Points:
Always skip the parent node in DFS to avoid false bridges.
You can avoid a visited[] array by checking the initialization value of disc or low.
The timer can be managed as an integer or a mutable array, depending on language and style.
To preserve input order, use a set or map to check the original direction of each connection.
A bridge is found when low[neighbor] > disc[current] after DFS.
import java.util.*;
classSolution {
int time = 0;
public List<List<Integer>>criticalConnections(int n, List<List<Integer>> connections) {
List<Integer>[] graph =new List[n];
for (int i = 0; i < n; i++) graph[i]=new ArrayList<>();
for (List<Integer> e : connections) {
graph[e.get(0)].add(e.get(1));
graph[e.get(1)].add(e.get(0));
}
List<List<Integer>> ans =new ArrayList<>();
int[] disc =newint[n], low =newint[n];
Arrays.fill(disc, -1);
for (int i = 0; i < n; i++)
if (disc[i]==-1) dfs(graph, i, -1, disc, low, ans);
return ans;
}
privatevoiddfs(List<Integer>[] graph, int u, int parent, int[] disc, int[] low, List<List<Integer>> ans) {
disc[u]= low[u]= time++;
for (int v : graph[u]) {
if (v == parent) continue;
if (disc[v]==-1) {
dfs(graph, v, u, disc, low, ans);
low[u]= Math.min(low[u], low[v]);
if (low[v]> disc[u]) ans.add(Arrays.asList(u, v));
} else {
low[u]= Math.min(low[u], disc[v]);
}
}
}
}
classSolution {
var time = 0funcriticalConnections(n: Int, connections: List<List<Int>>): List<List<Int>> {
val g = Array(n) { mutableListOf<Int>() }
for (e in connections) {
g[e[0]].add(e[1]); g[e[1]].add(e[0])
}
val disc = IntArray(n) { -1 }
val low = IntArray(n)
val ans = mutableListOf<List<Int>>()
fundfs(u: Int, parent: Int) {
disc[u] = time; low[u] = time; time++for (v in g[u]) {
if (v == parent) continueif (disc[v] == -1) {
dfs(v, u)
low[u] = minOf(low[u], low[v])
if (low[v] > disc[u]) ans.add(listOf(u, v))
} else {
low[u] = minOf(low[u], disc[v])
}
}
}
for (i in0 until n) if (disc[i] == -1) dfs(i, -1)
return ans
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classSolution:
defcriticalConnections(self, n: int, connections: list[list[int]]) -> list[list[int]]:
g = [[] for _ in range(n)]
for u, v in connections:
g[u].append(v); g[v].append(u)
disc = [-1]*n; low = [0]*n; time = [0]; ans = []
defdfs(u, parent):
disc[u] = low[u] = time[0]; time[0] +=1for v in g[u]:
if v == parent: continueif disc[v] ==-1:
dfs(v, u)
low[u] = min(low[u], low[v])
if low[v] > disc[u]: ans.append([u, v])
else:
low[u] = min(low[u], disc[v])
for i in range(n):
if disc[i] ==-1: dfs(i, -1)
return ans