AlgoMaster Logo

Critical Connections in a Network

hardFrequencyUpdated September 21, 2026

Understanding the Problem

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.

Key Constraints:

  • 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).
  • No repeated connections and no self-loops: the graph is simple. This matters in Approach 2, where the parent check relies on there being at most one edge between any two nodes.

Approach 1: Brute Force (Remove Each Edge)

Intuition

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.

Algorithm

  1. Build an adjacency list from the connections.
  2. For each edge (u, v) in the connections list:
    • Temporarily remove the edge (u, v) from the adjacency list.
    • Run BFS or DFS starting from u and check whether v is reachable.
    • If v is not reachable, the edge is a bridge. Add it to the result.
    • Restore the edge.
  3. Return all identified bridges.

Example Walkthrough

1Try removing edge [0,1]: BFS from 0 still reaches 1 (via 0→2→1). Not a bridge.
0213
1/5

Code

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.

Approach 2: Tarjan's Bridge-Finding Algorithm (Optimal)

Intuition

Running DFS on a connected undirected graph produces a DFS tree, and every edge of the original graph falls into one of two categories:

  1. Tree edges - edges that are part of the DFS tree (the edges traversed to discover new nodes).
  2. Back edges - edges that connect a node to an already-visited ancestor in the DFS tree.

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:

  • disc[v] (discovery time): the order in which DFS first visits node v.
  • low[v] (low-link value): the minimum discovery time reachable from v's subtree, considering both tree edges and back edges.

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.

Algorithm

  1. Build an adjacency list from the connections.
  2. Initialize arrays disc and low of size n, filled with -1 (unvisited).
  3. Start DFS from node 0 with parent -1 and a global timer starting at 0.
  4. For each node u during DFS:
    • Set disc[u] = low[u] = timer++.
    • For each neighbor v of u:
      • If v is the parent of u in the DFS tree, skip it.
      • If v is unvisited, recurse into v with parent u. After returning, update low[u] = min(low[u], low[v]). If low[v] > disc[u], add edge (u, v) to the result.
      • If v is already visited (and is not the parent), it is a back edge. Update low[u] = min(low[u], disc[v]).
  5. Return the collected bridges.

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.

Example Walkthrough

1Start DFS from node 0: disc[0]=0, low[0]=0
0disc=0, low=0123
1/9

Code