Graphs · Medium

183. Detect a Cycle in an Undirected Graph

Topics: Graph

Solve it on DeVert Campus - free code runner

Problem

Given an undirected graph with n vertices and m edges, print 'Yes' if it contains a cycle, otherwise 'No'.

Input and output

Input: The first line contains n, the number of vertices. The second line contains m, the number of edges. Each of the next m lines contains two integers, an undirected edge. Output: Print 'Yes' if the graph contains a cycle, otherwise print 'No'.

Example

Input:
4
4
0 1
1 2
2 3
3 0
Output:
Yes

Constraints

1 <= n <= 10^5

Key observation

This is meaningfully different from cycle detection in a DIRECTED graph (which needs to track the current recursion stack, not just parent) - undirected and directed cycle detection use genuinely different techniques, easy to conflate.

Intuition

Check whether an undirected graph contains a cycle. Brute force: Try to find some sequence of edges forming a closed loop by exhaustively checking all possible paths - this is far more work than a single DFS/BFS pass tracking parents. Optimised: Tracking each vertex's PARENT during a single DFS/BFS pass lets you distinguish a genuine cycle (reaching an already-visited vertex that ISN'T your immediate parent) from simply walking back along the SAME edge you just came from (which is normal and expected in undirected graph traversal, not a cycle).

Optimal - Union-Find (Disjoint Set Union)

Process the edges one at a time with a disjoint-set-union structure (union by rank/size plus path compression): for each edge (u, v), check whether u and v are already in the same set - if so, this edge connects two already-connected vertices, meaning a cycle exists - otherwise union their sets. This is iterative rather than recursive, so it avoids the stack-depth risk that DFS has on a skewed graph, and each union/find operation runs in near-constant amortised time thanks to the inverse Ackermann bound, making it both simple and robust at the given scale. Time: O(m * α(n)) Space: O(n)

Better - DFS With Parent Tracking

Run a DFS from each unvisited vertex, passing along the parent it was reached from; if the DFS ever encounters a neighbour that has already been visited and is not the immediate parent, a back edge has been found, meaning a cycle exists. This performs a single traversal of the whole graph rather than one per edge, giving a direct linear-time answer, though on a graph with up to 10^5 vertices arranged as a long path, recursive DFS risks a deep call stack. Time: O(n+m) Space: O(n)

Brute Force - Remove Each Edge and Recheck Connectivity

For every edge (u, v) in the graph, temporarily remove it and run a BFS or DFS to check whether u and v are still connected through some other route; if they are, that edge was part of a cycle, so the graph contains one. Checking all m edges this way is correct - a cycle exists exactly when some edge is 'redundant' for connectivity - but it repeats a full graph traversal once per edge, which is very wasteful. Time: O(m*(n+m)) Space: O(n+m)

Step by step

1. Do a DFS (or BFS) from every unvisited vertex, tracking each vertex's PARENT in the traversal. 2. If you reach an ALREADY-VISITED vertex that ISN'T the current vertex's immediate parent, you've found a cycle. 3. If no such situation ever arises across the whole graph, there's no cycle.

Complexity

Time: O(n + m) - one DFS/BFS pass (across all components) visiting every vertex and edge once. Space: O(n), for the visited array, parent tracking, and traversal stack/queue.

Edge cases

Test m = 0 (no edges, expect 'No'), a disconnected graph where only one component contains a cycle, and n at the upper bound of 10^5.

Interview tip

Be ready to explain WHY tracking parent is essential here specifically for UNDIRECTED graphs - without it, walking back along the same edge you just traversed would be incorrectly flagged as a cycle every single time.

All DSA problems