There are n
servers numbered from 0
to n - 1
connected by undirected server-to-server connections
forming a network where connections[i] = [a_i, b_i]
represents a connection between servers a_i
and b_i
. 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: n = 4, connections = [[0,1],[1,2],[2,0],[1,3]] Output: [[1,3]]
n = 2, connections = [[0,1]] Output: [[0,1]]
Constraints: 2 <= n <= 10^5 n - 1 <= connections.length <= 10^5 0 <= a_i, b_i <= n - 1 a_i != b_i There are no repeated connections.
## Critical Connections in a Network
This problem asks us to find the critical connections in a given network. A critical connection is one that, if removed, would disconnect the network, making some servers unreachable from others.
### Naive Approach (Brute Force)
One simple approach is to iterate through each connection, remove it from the graph, and then check if the graph is still connected. If the graph becomes disconnected after removing a connection, then that connection is critical.
**Code (Python):**
```python
def is_connected(n, adj): # Helper function to check connectivity
visited = [False] * n
q = [0] # Start BFS from node 0
visited[0] = True
count = 0
while q:
u = q.pop(0)
count += 1
for v in adj[u]:
if not visited[v]:
visited[v] = True
q.append(v)
return count == n
def critical_connections_naive(n, connections):
critical = []
for i in range(len(connections)):
# Remove the connection
temp_connections = connections[:i] + connections[i+1:]
# Build adjacency list
adj = [[] for _ in range(n)]
for u, v in temp_connections:
adj[u].append(v)
adj[v].append(u)
# Check if the graph is still connected
if not is_connected(n, adj):
critical.append(connections[i])
return critical
# Example Usage
n = 4
connections = [[0, 1], [1, 2], [2, 0], [1, 3]]
result = critical_connections_naive(n, connections)
print(result) # Output: [[1, 3]]
A more efficient approach is to use Tarjan's algorithm. Tarjan's algorithm is a graph traversal algorithm that can find bridges (critical connections) in a graph in linear time.
Key Concepts in Tarjan's Algorithm:
disc[u]
(Discovery Time): The time when a node u
is first visited during DFS.low[u]
(Lowest Reachable Ancestor): The lowest discovery time of any node reachable from u
through the DFS subtree rooted at u
(including back edges).Algorithm Steps:
disc
and low
arrays with -1
for all nodes.disc[u]
when a node u
is visited.low[u]
with disc[u]
initially.v
of u
:
v
is not visited, recursively call DFS on v
.
low[u] = min(low[u], low[v])
.low[v] > disc[u]
, then the edge (u, v)
is a critical connection (bridge).v
is visited and v
is not the parent of u
in the DFS tree, update low[u] = min(low[u], disc[v])
(back edge).Code (Python):
def critical_connections(n, connections):
adj = [[] for _ in range(n)]
for u, v in connections:
adj[u].append(v)
adj[v].append(u)
disc = [-1] * n
low = [-1] * n
critical = []
time = 0
def dfs(u, parent):
nonlocal time
disc[u] = time
low[u] = time
time += 1
for v in adj[u]:
if v == parent:
continue # Skip the parent
if disc[v] == -1:
dfs(v, u)
low[u] = min(low[u], low[v])
if low[v] > disc[u]:
critical.append([u, v] if u < v else [v, u]) # Ensure consistent ordering
else:
low[u] = min(low[u], disc[v]) # Back edge
for i in range(n):
if disc[i] == -1:
dfs(i, -1)
return critical
# Example Usage
n = 4
connections = [[0, 1], [1, 2], [2, 0], [1, 3]]
result = critical_connections(n, connections)
print(result) # Output: [[1, 3]]
visited
array in the is_connected
function takes O(V) space. Therefore, the overall space complexity is O(V + E).disc
and low
arrays (O(V) each), and the critical
list to store the results (in the worst case, O(E)). Therefore, the overall space complexity is O(V + E).These edge cases are handled correctly by Tarjan's algorithm.