Clone Graph Solution
Given a reference to a node in a connected undirected graph, return a deep copy of the graph. Each node has a value and a list of neighbors.
Problem Statement
Given a reference to a node in a connected undirected graph, return a deep copy. Each node contains int val and List<Node> neighbors. Nodes are labeled 1 to n. The graph may contain cycles.
Naive DFS Without Cycle Tracking
Recursively clone each node by creating a new node and cloning all its neighbors. Without tracking already-cloned nodes, cycles cause infinite recursion β the DFS enters an infinite loop following back edges.
The graph can contain cycles (e.g., node 1 β node 2 β node 1). Without a visited map, the DFS follows 1β2β1β2β... forever, causing a stack overflow.
HashMap<Node, Node> mapping each original node to its clone. Before recursing into a neighbor, check if it's already in the map. If yes, return the existing clone β breaking the cycle. If no, create the clone first (add to map), then wire up neighbors. The map serves as both a visited set and a clone registry.
| Metric | Value |
|---|---|
| Time | O(V + E) β every node and edge visited once |
| Space | O(V) β HashMap + BFS queue |
Recursive DFS with HashMap β O(V+E)
Recursively clone each node. The HashMap breaks cycles: check if a node is already cloned before recursing. If cloned, return the existing clone immediately β this is both the visited check and the cycle breaker.
- Base case: if
node == null, return null. - Cycle check: if
nodeis already incloneMap, returncloneMap.get(node). - Create: make a new node with same val, put in map immediately (before recursing β critical for cycles).
- Recurse: for each neighbor, recursively clone and add to the new node's neighbors list.
- Return the new clone.
- Any "deep copy of a graph / linked list with random pointers" problem.
- The signal is a data structure with references that may form cycles.
- The HashMap pattern solves LC 138 (Copy List with Random Pointer) with identical logic β map original to clone, then wire up pointers.
- The key: always add to the map before recursing into neighbors.
- If you create the clone and add to map only after processing all neighbors, you'll recurse infinitely on cycles before ever adding anything.
- Adding to the map first β even before setting up neighbors β breaks the cycle because the second time you encounter that node, it's already in the map and you return immediately.
Visual Walkthrough
Trace DFS cloning a 4-node cycle: 1β2β3β4β1. Starting from node 1.
Code β Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| DFS without map (broken) | O(β) | O(β) | Infinite recursion on cycles |
| BFS with HashMap | O(V + E) | O(V) | Iterative β no stack overflow; slightly more code |
| DFS with HashMap β optimal | O(V + E) | O(V) | Clean recursive code; map serves as both visited set and clone registry |
- Both are O(V+E). DFS requires O(V) call stack space in addition to the HashMap.
- BFS uses an explicit queue (O(V)) but no call stack.
- For very large graphs (V=100 per constraints β fine either way), BFS is safer in production.
- Both are valid in interviews.
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| Null input | node = null | Empty graph. Return null immediately β no map lookup needed. |
| Single node, no neighbors | Node(1, []) | Clone with empty neighbors list. DFS returns immediately after creating clone. |
| Cycle of 2 | 1β2 | DFS(1) creates clone(1), recurse into 2. DFS(2) creates clone(2), recurse into 1 β already in map, return clone(1). β |
| Star graph | Center + N leaves | Center has N neighbors, leaves have 1 each. DFS fans out correctly. |
| Adding to map after recursing | any cycle | If you add to map only after the recursive call returns, cycles cause infinite recursion before any map entry is ever created. |
| Negative or zero node values | Not possible per constraints | Node.val β [1,100] β no edge case here. The algorithm is value-agnostic anyway. |
cloneMap.put(node, clone) after the neighbor recursion loop instead of before. Consider node A β node B. DFS(A) creates clone(A), recurses into B. DFS(B) creates clone(B), recurses into A β but A is not yet in the map (you haven't returned from the A recursion yet), so DFS(A) is called again, causing infinite recursion. Always put the new clone into the map before iterating over neighbors.