There are n
servers numbered from 0
to n - 1
connected by undirected server-to-server connections
forming a network where connections[i] = [a<sub>i</sub>, b<sub>i</sub>]
represents a connection between servers a<sub>i</sub>
and b<sub>i</sub>
. 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.
For example:
Given n = 4
and connections = [[0,1],[1,2],[2,0],[1,3]]
, the expected output is [[1,3]]
. This is because removing the connection [1,3]
would disconnect server 3
from the rest of the network. The order doesn't matter, so [[3,1]]
is also acceptable.
Another example:
Given n = 2
and connections = [[0,1]]
, the expected output is [[0,1]]
. Removing this single connection would disconnect server 0
and server 1
.
Could you provide an algorithm to find all critical connections in the network, considering that 2 <= n <= 10^5
and n - 1 <= connections.length <= 10^5
?
Let's discuss how to find critical connections in a network.
A straightforward but inefficient approach would be to iterate through each connection, remove it temporarily, and then check if the graph remains connected. If removing the connection disconnects the graph, then it's a critical connection.
Big O Analysis (Naive Approach)
This approach is too slow for large graphs due to repeated graph traversals.
A more efficient solution uses Tarjan's algorithm, which is based on Depth-First Search (DFS). It identifies critical connections (also known as bridges) in O(V + E) time.
The key idea is to maintain two arrays:
disc[u]
: Discovery time of node u
during DFS.low[u]
: The lowest discovery time of any node reachable from u
(including u
itself) through the DFS subtree.Here's how it works:
disc[u]
and low[u]
to -1 for all nodes.time
to 0.u
:
disc[u] = low[u] = time++
v
of u
:
v
is not visited:
v
.low[u] = min(low[u], low[v])
low[v] > disc[u]
, then the edge (u, v)
is a critical connection.v
is not the parent of u
in the DFS tree (to avoid back-tracking immediately):
low[u] = min(low[u], disc[v])
Big O Analysis (Tarjan's Algorithm)
disc
, low
, and visited
arrays, and the recursion stack.from collections import defaultdict
def criticalConnections(n, connections):
graph = defaultdict(list)
for u, v in connections:
graph[u].append(v)
graph[v].append(u)
disc = [-1] * n
low = [-1] * n
time = 0
result = []
def dfs(u, parent):
nonlocal time
disc[u] = low[u] = time
time += 1
for v in graph[u]:
if v == parent:
continue
if disc[v] == -1:
dfs(v, u)
low[u] = min(low[u], low[v])
if low[v] > disc[u]:
result.append([u, v])
else:
low[u] = min(low[u], disc[v])
dfs(0, -1) # Start DFS from node 0
return result
In summary, Tarjan's algorithm provides an efficient way to find critical connections in a graph by leveraging DFS and tracking discovery times and low-link values.