--- Difficulty: Hard Source: 160 Days of Problem Solving Tags: - Graph --- # 🚀 _Day 9. Articulation Point - II_ 🧠 ## 💡 **Problem Description:** The problem can be found at the following link: [Question Link](https://www.geeksforgeeks.org/batch/gfg-160-problems/track/graph-gfg-160/problem/articulation-point2616) Given an undirected graph with **V** vertices and **E** edges, the graph is provided as a 2D array `edges[][]` where each element `edges[i] = [u, v]` indicates an undirected edge between vertices **u** and **v**. Your task is to return all the **articulation points** (or **cut vertices**) in the graph. An articulation point is a vertex whose removal (along with all its connected edges) increases the number of connected components in the graph. > **Note:** The graph may be disconnected, i.e., it may consist of more than one connected component. > If no such point exists, return `{-1}`. > _Note: Sorry for the delayed upload; my Final Semester exams are underway. Best of luck with your coding journey!_ ## 🔍 **Example Walkthrough:** #### **Input:** ``` V = 5 edges = [[0, 1], [1, 4], [4, 3], [4, 2], [2, 3]] ``` #### **Output:** ``` [1, 4] ``` #### **Explanation:** Removing vertex **1** or **4** disconnects the graph. For instance, if vertex **1** is removed, the part of the graph connected to **1** becomes isolated from the rest of the components. ### **Example 2:** #### **Input:** ``` V = 4 edges = [[0, 1], [0, 2]] ``` #### **Output:** ``` [0] ``` #### **Explanation:** Removing vertex **0** will increase the number of disconnected components from 1 to 3. ## **Constraints** - **1 ≤ V, E ≤ 10⁴** ## 🎯 **My Approach:** ### **Optimized DFS Using Tarjan’s Algorithm** ### **Algorithm Steps:** 1. **Convert the edge list to an adjacency list:** Build an adjacency list from the given edges to represent the graph. 2. **Use Tarjan’s DFS-based algorithm:** During the DFS traversal, maintain two arrays: - **`disc[u]`:** The discovery time of vertex **u**. - **`low[u]`:** The lowest discovery time reachable from **u** (including back-edges). 3. **Identify Articulation Points:** - For a **non-root vertex** **u**, if there is a child **v** such that `low[v] >= disc[u]`, then **u** is an articulation point. - For the **root vertex** of the DFS, if it has **more than one child**, then it is an articulation point. 4. **Handle Disconnected Graphs:** Since the graph can be disconnected, run the DFS for every unvisited vertex. ## 🕒 **Time and Auxiliary Space Complexity** - **Expected Time Complexity:** O(V + E), as every vertex and edge is visited only once in the DFS traversal. - **Expected Auxiliary Space Complexity:** O(V + E), primarily for the recursion stack, the adjacency list, and the additional arrays used for tracking discovery times and low values. ## 📝 **Solution Code** ## **Code (C++)** ```cpp class Solution { public: vector articulationPoints(int V, vector>& edges) { vector> adj(V); for (auto& e : edges) adj[e[0]].push_back(e[1]), adj[e[1]].push_back(e[0]); vector disc(V, -1), low(V), res; vector ap(V), vis(V); int time = 0; function dfs = [&](int u, int p) { vis[u] = true; disc[u] = low[u] = time++; int children = 0; for (int v : adj[u]) { if (v == p) continue; if (!vis[v]) { dfs(v, u); low[u] = min(low[u], low[v]); if (p != -1 && low[v] >= disc[u]) ap[u] = true; ++children; } else { low[u] = min(low[u], disc[v]); } } if (p == -1 && children > 1) ap[u] = true; }; for (int i = 0; i < V; ++i) if (!vis[i]) dfs(i, -1); for (int i = 0; i < V; ++i) if (ap[i]) res.push_back(i); return res.empty() ? vector{-1} : res; } }; ```

⚡ Alternative Approaches

## 📊 **2️⃣ Biconnected Components (Using Modified Tarjan’s Algorithm)** This approach builds upon Tarjan’s algorithm by identifying biconnected components. The vertices that appear in more than one biconnected component are articulation points. #### **Algorithm Steps:** 1. **Perform DFS** to compute discovery times (`disc[u]`) and low values (`low[u]`). 2. **Maintain a stack** of edges as part of the DFS to capture biconnected components. 3. For each vertex _u_, if a child _v_ satisfies `low[v] >= disc[u]`, then _u_ is an articulation point. 4. Optionally, you can **pop edges** from the stack to report each biconnected component. ## **C++ Code:** ```cpp class Solution { public: void dfs(int u, int parent, vector>& adj, vector& disc, vector& low, vector& visited, stack& st, vector& ap, int& time) { visited[u] = true; disc[u] = low[u] = ++time; int children = 0; for (int v : adj[u]) { if (!visited[v]) { children++; st.push(u); // Optionally use the stack to collect edges (for biconnected components) dfs(v, u, adj, disc, low, visited, st, ap, time); low[u] = min(low[u], low[v]); // Check articulation condition if ((parent != -1 && low[v] >= disc[u]) || (parent == -1 && children > 1)) ap[u] = true; } else if (v != parent) { low[u] = min(low[u], disc[v]); } } } vector articulationPoints(int V, vector>& edges) { vector> adj(V); for (auto &e : edges) { adj[e[0]].push_back(e[1]); adj[e[1]].push_back(e[0]); } vector disc(V, -1), low(V, -1); vector visited(V, false), ap(V, false); stack st; // optional: stores edges (u, v) for biconnected component analysis int time = 0; for (int i = 0; i < V; i++) if (!visited[i]) dfs(i, -1, adj, disc, low, visited, st, ap, time); vector res; for (int i = 0; i < V; i++) if (ap[i]) res.push_back(i); return res.empty() ? vector{-1} : res; } }; ``` #### 📝 **Complexity Analysis:** - **Time Complexity:** O(V + E) - **Space Complexity:** O(V + E) (includes the recursion stack and the edge stack) #### ✅ **Why Use This Approach?** - **Comprehensive Analysis:** It not only finds articulation points but also helps in identifying biconnected components. - **Applications:** Particularly useful when the structure of biconnected components is needed, as in network reliability studies. ### 🆚 **Comparison of Approaches** | **Approach** | ⏱️ **Time Complexity** | 🗂️ **Space Complexity** | ✅ **Pros** | ⚠️ **Cons** | | ---------------------------------------------- | ---------------------- | ----------------------- | ------------------------------------------------------ | ----------------------------------------------- | | **Tarjan’s Algorithm** | 🟢 O(V + E) | 🟢 O(V + E) | Optimal, widely used, and efficient | Recursive (may hit limits for very deep graphs) | | **Biconnected Components (Modified Tarjan’s)** | 🟢 O(V + E) | 🟡 O(V + E) | Provides extra insight into the structure of the graph | Slightly more complex to implement | ✅ **Best Choice?** - For efficient and optimal detection of articulation points, **Tarjan’s algorithm** is the best choice. - The **biconnected components approach** is useful when additional component structure is required.
## **Code (Java)** ```java class Solution { static ArrayList articulationPoints(int V, int[][] edges) { ArrayList[] adj = new ArrayList[V]; for (int i = 0; i < V; i++) adj[i] = new ArrayList<>(); for (int[] e : edges) { adj[e[0]].add(e[1]); adj[e[1]].add(e[0]); } int[] disc = new int[V], low = new int[V]; boolean[] vis = new boolean[V], ap = new boolean[V]; int[] time = {0}; for (int i = 0; i < V; i++) if (!vis[i]) dfs(i, -1, adj, disc, low, vis, ap, time); ArrayList res = new ArrayList<>(); for (int i = 0; i < V; i++) if (ap[i]) res.add(i); if (res.isEmpty()) res.add(-1); return res; } static void dfs(int u, int p, ArrayList[] adj, int[] disc, int[] low, boolean[] vis, boolean[] ap, int[] time) { vis[u] = true; disc[u] = low[u] = ++time[0]; int children = 0; for (int v : adj[u]) { if (!vis[v]) { children++; dfs(v, u, adj, disc, low, vis, ap, time); low[u] = Math.min(low[u], low[v]); if (p != -1 && low[v] >= disc[u]) ap[u] = true; } else if (v != p) low[u] = Math.min(low[u], disc[v]); } if (p == -1 && children > 1) ap[u] = true; } } ``` ## **Code (Python)** ```python class Solution: def articulationPoints(self, V, edges): adj = [[] for _ in range(V)] for u, v in edges: adj[u].append(v) adj[v].append(u) disc = [-1] * V low = [-1] * V vis = [False] * V ap = [False] * V time = [0] def dfs(u, p): vis[u] = True disc[u] = low[u] = time[0] = time[0] + 1 children = 0 for v in adj[u]: if not vis[v]: children += 1 dfs(v, u) low[u] = min(low[u], low[v]) if p != -1 and low[v] >= disc[u]: ap[u] = True elif v != p: low[u] = min(low[u], disc[v]) if p == -1 and children > 1: ap[u] = True for i in range(V): if not vis[i]: dfs(i, -1) res = [i for i, val in enumerate(ap) if val] return res if res else [-1] ``` ## 🎯 **Contribution and Support:** For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: [Any Questions](https://www.linkedin.com/in/patel-hetkumar-sandipbhai-8b110525a/). Let’s make this learning journey more collaborative! ⭐ **If you find this helpful, please give this repository a star!** ⭐ ---

📍Visitor Count