We are given an undirected graph of servers and connections. We need to find all "bridges" in this graph, meaning edges whose removal would disconnect the graph (or at least disconnect some part of it from another).
A road network is a useful analogy. Most cities have multiple routes between them, but sometimes a single road connects two regions. If that road closes, the regions become isolated. A critical connection is that single road.
Checking each edge independently, by removing it and running a connectivity check, is too slow at this input size. Tarjan's bridge-finding algorithm finds every bridge in a single DFS by tracking discovery times and low-link values.
An edge (u, v) is a bridge if and only if no back edge from the subtree rooted at v (in the DFS tree) reaches u or any ancestor of u. When no such back edge exists, the edge (u, v) is the only link between v's subtree and the rest of the graph.
2 <= n <= 10^5: with up to 100,000 nodes, the algorithm must be linear or near-linear. O(n^2) is about 10^10 operations, far beyond what runs in time.n - 1 <= connections.length <= 10^5: the graph is sparse, and the lower bound of n - 1 edges means it is connected (it contains at least a spanning tree).The definition of a bridge can be tested directly: remove an edge (u, v) and check whether u can still reach v through the rest of the graph. If u can no longer reach v, then that edge was the only path between them, so it is a critical connection. We repeat this test for every edge.
Checking reachability between the two endpoints is more reliable than counting how many nodes are reachable from a fixed start node. A node that is never mentioned in the connections list stays isolated regardless of which edge we remove, so a global count would always fall short and wrongly flag every edge.
This approach is correct but repeats a full graph rebuild and BFS for every edge. Tarjan's algorithm finds all bridges in one DFS traversal.
Running DFS on a connected undirected graph produces a DFS tree, and every edge of the original graph falls into one of two categories:
Back edges are important because they create cycles. If there is a back edge from some node in v's subtree to u or an ancestor of u, then even if we remove the tree edge (u, v), we can still reach v's subtree through the back edge. But if no such back edge exists, removing (u, v) disconnects v's subtree from the rest of the graph, making (u, v) a bridge.
To detect this during the traversal, assign each node two values:
The low-link value captures the "earliest ancestor" that v's subtree can reach via a back edge. If low[v] > disc[u], it means v's subtree cannot reach u or any ancestor of u through any path other than the edge (u, v). So (u, v) is a bridge.
DFS on an undirected graph produces no cross edges: every non-tree edge connects a node to one of its ancestors. This is what makes the two-category split (tree edge or back edge) exhaustive, and it is the property that fails for directed graphs.
An edge is a bridge if and only if it lies on no cycle. A back edge from v's subtree to u or above closes a cycle through the tree edge (u, v), and low[v] <= disc[u] holds precisely when such a back edge exists. The test low[v] > disc[u] therefore flags exactly the edges that lie on no cycle.
disc and low of size n, filled with -1 (unvisited).disc[u] = low[u] = timer++.low[u] = min(low[u], low[v]). If low[v] > disc[u], add edge (u, v) to the result.low[u] = min(low[u], disc[v]).Skipping the parent by node id in step 4 is safe only because the problem guarantees no repeated connections. If parallel edges were allowed, a second (u, v) edge would itself be an alternative path between u and v, and the skip would have to compare edge ids instead of node ids.