Cycle Detection in Undirected Graph in DSA Typescript - Time & Space Complexity
We want to understand how long it takes to check if an undirected graph has a cycle.
The question is: how does the time grow as the graph gets bigger?
Analyze the time complexity of the following code snippet.
function hasCycle(graph: Map<number, number[]>): boolean {
const visited = new Set<number>();
function dfs(node: number, parent: number): boolean {
visited.add(node);
for (const neighbor of graph.get(node) || []) {
if (!visited.has(neighbor)) {
if (dfs(neighbor, node)) return true;
} else if (neighbor !== parent) {
return true;
}
}
return false;
}
for (const node of graph.keys()) {
if (!visited.has(node)) {
if (dfs(node, -1)) return true;
}
}
return false;
}
This code checks each node and uses depth-first search to find if any cycle exists in the graph.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The depth-first search (DFS) visits each node and its neighbors.
- How many times: Each node and edge is visited once during DFS.
As the graph grows, the DFS explores all nodes and edges once.
| Input Size (n nodes, e edges) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 25 visits (nodes + edges) |
| 100 nodes, 200 edges | About 300 visits |
| 1000 nodes, 3000 edges | About 4000 visits |
Pattern observation: The operations grow roughly in proportion to the number of nodes plus edges.
Time Complexity: O(n + e)
This means the time to detect a cycle grows linearly with the total nodes and edges in the graph.
[X] Wrong: "Cycle detection takes O(n²) time because of nested loops."
[OK] Correct: The DFS visits each node and edge only once, so it does not repeat work unnecessarily.
Understanding this time complexity helps you explain how graph traversal algorithms work efficiently in real problems.
"What if the graph was directed instead of undirected? How would the time complexity change?"
Practice
Solution
Step 1: Understand DFS traversal in undirected graph
DFS visits nodes and tracks the parent node to avoid false cycle detection.Step 2: Detect cycle condition
If during DFS, a visited node is found that is not the parent, it means a back edge exists, indicating a cycle.Final Answer:
If we visit a node that is already visited and is not the parent, a cycle exists. -> Option AQuick Check:
Cycle detection = visit visited node not parent [OK]
- Ignoring the parent node check causes false positives.
- Assuming more edges than nodes always means cycle.
- Confusing disconnected graph with cycle presence.
Solution
Step 1: Identify required parameters for DFS cycle detection
We need current node, parent node, visited array, and graph adjacency list.Step 2: Match function signature with parameters and return type
function dfs(node: number, parent: number, visited: boolean[], graph: number[][]): boolean has all parameters and returns boolean indicating cycle presence.Final Answer:
function dfs(node: number, parent: number, visited: boolean[], graph: number[][]): boolean -> Option AQuick Check:
DFS needs node, parent, visited, graph, returns boolean [OK]
- Missing parent parameter causes incorrect cycle detection.
- Wrong return type (void instead of boolean).
- Using wrong node type (string instead of number).
const graph = [[1,2],[0,2],[0,1,3],[2]];
const visited = new Array(4).fill(false);
function dfs(node: number, parent: number): boolean {
visited[node] = true;
for (const neighbor of graph[node]) {
if (!visited[neighbor]) {
if (dfs(neighbor, node)) return true;
} else if (neighbor !== parent) {
return true;
}
}
return false;
}
console.log(dfs(0, -1));Solution
Step 1: Analyze graph structure and DFS traversal
Graph edges: 0-1, 0-2, 1-2, 2-3. Nodes 0,1,2 form a triangle (cycle).Step 2: Trace DFS calls from node 0
DFS visits 0, then 1, then 2. At 2, neighbor 0 is visited and not parent, cycle detected, returns true.Final Answer:
true -> Option BQuick Check:
Triangle cycle detected = true [OK]
- Ignoring parent check causes false cycle detection.
- Assuming no cycle in triangle graph.
- Confusing return values of DFS function.
function dfs(node: number, parent: number, visited: boolean[], graph: number[][]): boolean {
visited[node] = true;
for (const neighbor of graph[node]) {
if (!visited[neighbor]) {
dfs(neighbor, node, visited, graph);
} else if (neighbor !== parent) {
return true;
}
}
return false;
}Solution
Step 1: Check recursive DFS call handling
The recursive call dfs(neighbor, node, visited, graph) is made but its boolean result is ignored.Step 2: Identify missing return for cycle detection
If recursive call returns true (cycle found), it should be returned immediately to propagate detection.Final Answer:
Missing return statement for recursive dfs call result. -> Option CQuick Check:
Return recursive DFS result to detect cycle [OK]
- Ignoring return value of recursive DFS calls.
- Not updating visited array before recursion.
- Misusing parent parameter in cycle check.
Solution
Step 1: Understand cycle detection with path tracking
Maintain a stack or list of nodes visited in current DFS path to track traversal.Step 2: Return cycle nodes when back edge found
When a visited node (not parent) is found, extract nodes from stack forming the cycle path.Final Answer:
Track the path stack during DFS and return the cycle path when detected. -> Option DQuick Check:
Use DFS path stack to get cycle nodes [OK]
- Using BFS which is less natural for cycle path retrieval.
- Assuming edge count alone identifies cycle nodes.
- Marking nodes visited twice without path context.
Ready to prove it?
Pass the test and this pattern moves unknown
