|
| 1 | +<h1 align='center'>Critical - Connections - in a - Network</h1> |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +**Problem URL :** [Critical Connections in a Network](https://leetcode.com/problems/critical-connections-in-a-network/) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +## Problem Explanation |
| 11 | +The problem **Critical Connections in a Network** asks us to find all the critical connections in a given network of servers represented as an undirected graph. A **critical connection** (also known as a **bridge**) is an edge in the network whose removal will increase the number of connected components. In simpler terms, if we remove that edge, the network will be split into two or more disconnected parts. |
| 12 | + |
| 13 | +#### Example: |
| 14 | + |
| 15 | +Consider the following network of servers: |
| 16 | + |
| 17 | +- `n = 4` (number of servers) |
| 18 | +- Connections (edges): `[[0,1], [1,2], [2,0], [1,3]]` |
| 19 | + |
| 20 | +This means: |
| 21 | +- Server `0` is connected to Server `1` and Server `2` |
| 22 | +- Server `1` is connected to Server `0`, Server `2`, and Server `3` |
| 23 | +- Server `2` is connected to Server `1` and Server `0` |
| 24 | +- Server `3` is connected to Server `1` |
| 25 | + |
| 26 | +The graph representation looks like this: |
| 27 | + |
| 28 | +``` |
| 29 | + 0 - 1 - 3 |
| 30 | + | | |
| 31 | + 2 - |
| 32 | +``` |
| 33 | + |
| 34 | +#### Objective: |
| 35 | +We need to identify the critical connections in the network. Let's analyze the connections: |
| 36 | + |
| 37 | +- If we remove the edge `(1, 3)`, we disconnect Server `3` from the rest of the network. So, `(1, 3)` is a critical connection. |
| 38 | +- If we remove the edge `(0, 1)`, the network will still remain connected through Server `2`. So, this is not a critical connection. |
| 39 | + |
| 40 | +Therefore, the only critical connection in this case is `[1, 3]`. |
| 41 | + |
| 42 | +#### Approach: |
| 43 | + |
| 44 | +1. **Understanding Bridges**: A bridge is an edge in an undirected graph that, when removed, increases the number of connected components. |
| 45 | + |
| 46 | +2. **DFS Traversal**: |
| 47 | + - We can use **Depth-First Search (DFS)** to explore the graph. During the DFS traversal, we keep track of: |
| 48 | + - **Discovery Time (`disc`)**: The time when a node is first visited. |
| 49 | + - **Lowest Point (`low`)**: The lowest discovery time reachable from a node, which includes its neighbors and any back edges. |
| 50 | + |
| 51 | +3. **Bridge Condition**: |
| 52 | + - For each edge `(u, v)`, if `low[v] > disc[u]`, then the edge `(u, v)` is a **bridge**. This is because there is no way to reach a node earlier than `u` from `v` through a back edge. |
| 53 | + |
| 54 | +4. **DFS Tree**: |
| 55 | + - We maintain a DFS tree and check for each edge if it's a bridge based on the `low` and `disc` values. |
| 56 | + |
| 57 | +## Problem Solution |
| 58 | +```cpp |
| 59 | +class Solution { |
| 60 | +public: |
| 61 | + // Helper function to perform DFS traversal and find critical connections |
| 62 | + void DFS(int server, int timer, int parentServer, vector<int>& low, vector<int>& disc, vector<int>& visited, vector<vector<int>>& criticalEdge, vector<vector<int>>& connections){ |
| 63 | + |
| 64 | + // Mark the current server as visited |
| 65 | + visited[server] = true; |
| 66 | + |
| 67 | + // Set discovery time and lowest reachable time for the current server |
| 68 | + disc[server] = low[server] = timer++; // timer is incremented after setting the discovery time |
| 69 | + |
| 70 | + // Explore all neighboring servers (connections) |
| 71 | + for(int & neighbourServer : connections[server]){ |
| 72 | + |
| 73 | + // Skip the parent server to avoid going back to the parent in the DFS tree |
| 74 | + if(neighbourServer == parentServer) continue; |
| 75 | + |
| 76 | + // If the neighboring server is not visited, explore it |
| 77 | + if(!visited[neighbourServer]){ |
| 78 | + DFS(neighbourServer, timer, server, low, disc, visited, criticalEdge, connections); |
| 79 | + |
| 80 | + // After visiting the neighbor, update the low time for the current server |
| 81 | + low[server] = min(low[server], low[neighbourServer]); |
| 82 | + |
| 83 | + // If the lowest time of the neighbor is greater than the discovery time of the current server, |
| 84 | + // it indicates that the edge between current server and the neighbor is a critical connection. |
| 85 | + if(low[neighbourServer] > disc[server]) |
| 86 | + criticalEdge.push_back({server, neighbourServer}); |
| 87 | + }else { |
| 88 | + // If the neighboring server was already visited, update the low time |
| 89 | + low[server] = min(low[server], low[neighbourServer]); |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + // Main function to find all critical connections in the network |
| 95 | + vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) { |
| 96 | + // Arrays to store the lowest time reachable from a server and the discovery time of each server |
| 97 | + vector<int> low(n, -1); |
| 98 | + vector<int> disc(n, -1); |
| 99 | + |
| 100 | + // Array to track if a server has been visited |
| 101 | + vector<int> visited(n, false); |
| 102 | + |
| 103 | + // To store the critical connections (edges) |
| 104 | + vector<vector<int>> criticalEdge; |
| 105 | + |
| 106 | + // Adjacency list to represent the network of servers |
| 107 | + vector<vector<int>> adjacencyList(n); |
| 108 | + |
| 109 | + // Build the adjacency list from the connections |
| 110 | + for(auto& edge : connections){ |
| 111 | + adjacencyList[edge[0]].push_back(edge[1]); |
| 112 | + adjacencyList[edge[1]].push_back(edge[0]); |
| 113 | + } |
| 114 | + |
| 115 | + // Start DFS from each server that hasn't been visited |
| 116 | + for(int i = 0; i < n; i++) |
| 117 | + if(!visited[i]) |
| 118 | + DFS(i, 0, -1, low, disc, visited, criticalEdge, adjacencyList); |
| 119 | + |
| 120 | + // Return the list of critical connections (edges) |
| 121 | + return criticalEdge; |
| 122 | + } |
| 123 | +}; |
| 124 | + |
| 125 | +``` |
| 126 | +
|
| 127 | +## Problem Solution Explanation |
| 128 | +
|
| 129 | +Now, let's go through the code to understand how it works. |
| 130 | +
|
| 131 | +#### `DFS` function: |
| 132 | +
|
| 133 | +```cpp |
| 134 | +void DFS(int server, int timer, int parentServer, vector<int>& low, vector<int>& disc, vector<int>& visited, vector<vector<int>>& criticalEdge, vector<vector<int>>& connections) { |
| 135 | +``` |
| 136 | +- This is the DFS function. It takes the following parameters: |
| 137 | + - `server`: The current server (node) being visited. |
| 138 | + - `timer`: A variable to keep track of the discovery time (increments with each visit). |
| 139 | + - `parentServer`: The parent of the current node, used to avoid revisiting the parent in DFS. |
| 140 | + - `low`: Stores the lowest discovery time reachable from a server. |
| 141 | + - `disc`: Stores the discovery time of each server. |
| 142 | + - `visited`: Tracks whether a server has been visited. |
| 143 | + - `criticalEdge`: Stores the critical connections (bridges). |
| 144 | + - `connections`: The adjacency list of the graph. |
| 145 | + |
| 146 | +```cpp |
| 147 | + visited[server] = true; // Mark the current server as visited |
| 148 | + disc[server] = low[server] = timer++; // Set the discovery and low time of the current server |
| 149 | +``` |
| 150 | +- The `server` is marked as visited. |
| 151 | +- We set the discovery time `disc[server]` and low time `low[server]` for this server to the current `timer` and then increment `timer`. |
| 152 | + |
| 153 | +```cpp |
| 154 | + for(int & neighbourServer : connections[server]) { |
| 155 | +``` |
| 156 | +- Loop through all the neighbors (connections) of the current server. |
| 157 | +
|
| 158 | +```cpp |
| 159 | + if(neighbourServer == parentServer) continue; // Skip the parent node to avoid going backward in the DFS |
| 160 | +``` |
| 161 | +- If the neighbor is the parent of the current server, skip it to avoid a backward traversal in the DFS tree. |
| 162 | + |
| 163 | +```cpp |
| 164 | + if(!visited[neighbourServer]) { |
| 165 | +``` |
| 166 | +- If the neighbor has not been visited, perform DFS on it. |
| 167 | +
|
| 168 | +```cpp |
| 169 | + DFS(neighbourServer, timer, server, low, disc, visited, criticalEdge, connections); |
| 170 | + low[server] = min(low[server], low[neighbourServer]); |
| 171 | +``` |
| 172 | +- Recursively call DFS for the unvisited neighbor. |
| 173 | +- After returning from the DFS call, update `low[server]` to the minimum of `low[server]` and `low[neighbourServer]`. |
| 174 | + |
| 175 | +```cpp |
| 176 | + if(low[neighbourServer] > disc[server]) criticalEdge.push_back({server, neighbourServer}); |
| 177 | +``` |
| 178 | +- If `low[neighbourServer] > disc[server]`, this means there is no back edge from `neighbourServer` to any ancestor of `server`, indicating that `(server, neighbourServer)` is a critical connection (bridge). |
| 179 | +
|
| 180 | +```cpp |
| 181 | + } else low[server] = min(low[server], disc[neighbourServer]); |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | +- If the neighbor is already visited, update `low[server]` to the minimum of its current value and the discovery time of the neighbor. |
| 186 | + |
| 187 | +#### `criticalConnections` function: |
| 188 | + |
| 189 | +```cpp |
| 190 | +vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) { |
| 191 | + vector<int> low(n, -1); // Low time for each server, initially set to -1 |
| 192 | + vector<int> disc(n, -1); // Discovery time for each server, initially set to -1 |
| 193 | + vector<int> visited(n, false); // Visited array to track visited servers |
| 194 | + vector<vector<int>> criticalEdge; // Stores critical connections (bridges) |
| 195 | + vector<vector<int>> adjacencyList(n); // Adjacency list of the graph |
| 196 | +``` |
| 197 | +- Initialize necessary arrays for `low`, `disc`, `visited`, and the result list `criticalEdge`. |
| 198 | +
|
| 199 | +```cpp |
| 200 | + for(auto& edge : connections) { |
| 201 | + adjacencyList[edge[0]].push_back(edge[1]); |
| 202 | + adjacencyList[edge[1]].push_back(edge[0]); |
| 203 | + } |
| 204 | +``` |
| 205 | +- Convert the list of connections into an adjacency list representation of the graph. |
| 206 | + |
| 207 | +```cpp |
| 208 | + for(int i = 0; i < n; i++) |
| 209 | + if(!visited[i]) DFS(i, 0, -1, low, disc, visited, criticalEdge, adjacencyList); |
| 210 | +``` |
| 211 | +- Run DFS from every unvisited server to ensure all servers are explored. |
| 212 | + |
| 213 | +```cpp |
| 214 | + return criticalEdge; // Return the list of critical connections (bridges) |
| 215 | +} |
| 216 | +``` |
| 217 | +- After DFS completes, return the list of critical connections. |
| 218 | + |
| 219 | +### Step 3: Example Walkthrough |
| 220 | + |
| 221 | +Let's use an example to demonstrate how the code works. |
| 222 | + |
| 223 | +#### Example 1: |
| 224 | +``` |
| 225 | +n = 4 |
| 226 | +connections = [[0,1], [1,2], [2,0], [1,3]] |
| 227 | +``` |
| 228 | + |
| 229 | +- Convert `connections` into an adjacency list: |
| 230 | + ``` |
| 231 | + adjacencyList = [[1, 2], [0, 2, 3], [0, 1], [1]] |
| 232 | + ``` |
| 233 | + |
| 234 | +- Run DFS starting from node `0`: |
| 235 | + - Visit node `0`, discovery time is `0`, low time is `0`. |
| 236 | + - Visit node `1`, discovery time is `1`, low time is `1`. |
| 237 | + - Visit node `2`, discovery time is `2`, low time is `2`. |
| 238 | + - Visit node `3`, discovery time is `3`, low time is `3`. |
| 239 | + - Backtrack and update low values. |
| 240 | + - Edge `(1, 3)` is identified as a critical connection because `low[3] > disc[1]`. |
| 241 | + |
| 242 | +Output: `[[1, 3]]` |
| 243 | + |
| 244 | +### Step 4: Time and Space Complexity |
| 245 | + |
| 246 | +- **Time Complexity**: |
| 247 | + - The DFS traversal visits each node and edge once. Therefore, the time complexity is O(V + E), where V is the number of vertices (servers) and E is the number of edges (connections). |
| 248 | + |
| 249 | +- **Space Complexity**: |
| 250 | + - We use several arrays to store the `disc`, `low`, and `visited` information, each of size O(V). The adjacency list also takes O(E) space. |
| 251 | + - Therefore, the space complexity is O(V + E). |
| 252 | + |
| 253 | +### Step 5: Additional Recommendations |
| 254 | + |
| 255 | +- Understand the concept of **DFS** and **low/discovery times** thoroughly, as they are key to identifying bridges. |
| 256 | +- Practice more problems related to graph traversal (DFS/BFS) and bridges to strengthen your problem-solving skills. |
| 257 | +- Try solving similar problems using different graph traversal techniques like BFS to get a better understanding. |
0 commit comments