LeetCode #133 Medium

Clone Graph

Clone Graph is LeetCode 133 (Medium). You are given a reference to one node of a connected, undirected graph. Return a deep copy of the whole graph: new node objects with the same values, wired together exactly like the original.

  • Each node has an integer val and a list neighbors.
  • Values are unique and run from 1 to the number of nodes, so a value identifies its node.
  • The copy must not share a single node with the original.
Constraints
  • The number of nodes in the graph is in the range [0, 100].
  • 1 <= Node.val <= 100
  • Node.val is unique for each node.
  • There are no repeated edges and no self-loops in the graph.
  • The Graph is connected and all nodes can be visited starting from the given node.
graphdfshash-table
Open on LeetCode ↗
02

Intuition

Copying one node is easy; the trouble is the edges. A naive recursive copy loops forever on a cycle, and copies a node twice when two paths reach it, so the result no longer has the same shape.

Both problems disappear with one hash map from original node to its copy. The map answers "have I copied this already?", so each node is copied once and every edge to it points at that single copy. The key detail is order: the copy goes into the map before its neighbours are visited, so when a cycle leads back, there is already a node to point at instead of another recursion.

How to spot this pattern

Any deep copy of a structure with shared or cyclic references (graphs, lists with random pointers) needs a map from old objects to new ones. The traversal (DFS or BFS) just decides the order in which nodes are reached.

03

Approach

Try it first

Before reading on, trace a plain recursive copy on the square graph 1-2-3-4-1. Where does it loop? What single piece of information would let it stop?

1

Handle the empty graph

If node is None, return None straight away. An empty graph is a valid input, and checking it first means the rest of the code can assume a real start node with a value and a neighbour list.

2

Keep a map of copies

Create copies, a map from each original node to its clone. It does two jobs at once: it is the visited set that stops cycles, and it is the lookup that makes every edge point at one shared copy instead of a fresh duplicate.

3

Copy with DFS

clone(n):

  • if n is in copies, return copies[n];
  • create copy = Node(n.val) and store copies[n] = copy;
  • for each neighbour, append clone(neighbour) to copy.neighbors;
  • return copy.

Recursion depth can reach the node count, so for very large graphs the same logic runs with an explicit stack or a BFS queue.

4

Return the copy of the start node

Return clone(node), the copy of the start node, and the clone graph solution is done. The graph is connected, so every node is reachable from the start and is copied exactly once, which gives O(V + E) time and O(V) space for the map.

04

Clone Graph solution in Python | C++ | Java

▶1class Solution:
▶2 def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
▶3 if not node:
▶4 return None
▶5 copies = {}
▶6 
▶7 def clone(n: "Node") -> "Node":
▶8 if n in copies:
▶9 return copies[n]
▶10 copy = Node(n.val)
▶11 copies[n] = copy
▶12 for nb in n.neighbors:
▶13 copy.neighbors.append(clone(nb))
▶14 return copy
▶15 
▶16 return clone(node)
originalcopy1234maporiginalcopy1234start at node 1
copies{}original node -> its copy
Deep copy with a map. A graph can have cycles, and a plain recursive copy would chase a cycle forever. A map from each original node to its copy fixes both problems: it stops revisits, and it lets a later edge point at a copy that already exists.
originalcopy12341'maporiginalcopy11'234create 1' → map
visiting1the start node
copies1 entriesstored before its neighbours are copied
Make the copy 1' and put it in the map before copying its neighbours. If a neighbour leads back to 1 through a cycle, the map already has 1' and the recursion stops.
originalcopy12341'2'maporiginalcopy11'22'34create 2' → map
visiting2reached from 1
copies2 entriesstored before its neighbours are copied
Make the copy 2' and put it in the map before copying its neighbours. If a neighbour leads back to 2 through a cycle, the map already has 2' and the recursion stops.
originalcopy12341'2'maporiginalcopy11'22'341' in map → 2' links it
edge2 – 1neighbour entry of 2
1'already in mapreuse, do not copy again
Neighbour 1 is already in the map, so 2' points at the existing 1'. This lookup is what breaks the cycle and keeps exactly one copy per node.
originalcopy12341'2'3'maporiginalcopy11'22'33'4create 3' → map
visiting3reached from 2
copies3 entriesstored before its neighbours are copied
Make the copy 3' and put it in the map before copying its neighbours. If a neighbour leads back to 3 through a cycle, the map already has 3' and the recursion stops.
originalcopy12341'2'3'maporiginalcopy11'22'33'42' in map → 3' links it
edge3 – 2neighbour entry of 3
2'already in mapreuse, do not copy again
Neighbour 2 is already in the map, so 3' points at the existing 2'. This lookup is what breaks the cycle and keeps exactly one copy per node.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'create 4' → map
visiting4reached from 3
copies4 entriesstored before its neighbours are copied
Make the copy 4' and put it in the map before copying its neighbours. If a neighbour leads back to 4 through a cycle, the map already has 4' and the recursion stops.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'1' in map → 4' links it
edge4 – 1neighbour entry of 4
1'already in mapreuse, do not copy again
Neighbour 1 is already in the map, so 4' points at the existing 1'. This lookup is what breaks the cycle and keeps exactly one copy per node.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'3' in map → 4' links it
edge4 – 3neighbour entry of 4
3'already in mapreuse, do not copy again
Neighbour 3 is already in the map, so 4' points at the existing 3'. This lookup is what breaks the cycle and keeps exactly one copy per node.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'3' links new 4'
edge3 – 4neighbour entry of 3
4'just builtreturned by the recursive call
The recursive call has finished building 4' (and everything reachable from it). Now 3' gets its neighbour entry pointing at it.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'2' links new 3'
edge2 – 3neighbour entry of 2
3'just builtreturned by the recursive call
The recursive call has finished building 3' (and everything reachable from it). Now 2' gets its neighbour entry pointing at it.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'1' links new 2'
edge1 – 2neighbour entry of 1
2'just builtreturned by the recursive call
The recursive call has finished building 2' (and everything reachable from it). Now 1' gets its neighbour entry pointing at it.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'4' in map → 1' links it
edge1 – 4neighbour entry of 1
4'already in mapreuse, do not copy again
Neighbour 4 is already in the map, so 1' points at the existing 4'. This lookup is what breaks the cycle and keeps exactly one copy per node.
originalcopy12341'2'3'4'maporiginalcopy11'22'33'44'return 1'
nodes copied4each exactly once
neighbour entries8two per undirected edge
Return the copy of the start node. Every original node has exactly one copy, and every neighbour list points only at copies, never back into the original graph. That is what makes it a deep copy.
05

BFS version (queue)

The same copy map with a queue instead of recursion. Create a neighbour's copy the moment it is first seen, before queueing it, so each node is copied once; then wire each popped node's copy to its neighbours' copies.

▶1from collections import deque
▶2 
▶3 
▶4class Solution:
▶5 def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
▶6 if not node:
▶7 return None
▶8 copies = {node: Node(node.val)}
▶9 queue = deque([node])
▶10 while queue:
▶11 n = queue.popleft()
▶12 for nb in n.neighbors:
▶13 if nb not in copies:
▶14 copies[nb] = Node(nb.val)
▶15 queue.append(nb)
▶16 copies[n].neighbors.append(copies[nb])
▶17 return copies[node]
06

Common pitfalls

Storing the copy after visiting the neighbours

✗ Wrong
copy = Node(n.val)
for nb in n.neighbors:
    copy.neighbors.append(clone(nb))
copies[n] = copy
✓ Right
copy = Node(n.val)
copies[n] = copy
for nb in n.neighbors:
    copy.neighbors.append(clone(nb))

A cycle leads back to n before it is in the map, so n is copied again, and again: infinite recursion. The map entry must exist before any neighbour is visited.

Keeping a visited set of original nodes only

✗ Wrong
if n in visited:
    return n
✓ Right
if n in copies:
    return copies[n]

Returning the original node wires the copy back into the old graph, so the result is not a deep copy. You need the copy, which is why the map stores old -> new.

07

Edge cases

Empty graph

node is None: return None before touching the map.

Single node, no neighbours

Input [[]]. One copy is made with an empty neighbors list; the loop over neighbours does nothing.

08

Complexity

Time
O(V + E)
Space
O(V)
Each node is copied once and each neighbour entry is appended once. The map holds V entries; DFS recursion adds up to O(V) stack.
09

Clone Graph FAQ

What does clone graph mean?

Build a new graph with the same values and the same connections, where every node is a new object. No node or neighbour list in the copy may point into the original graph.

How do you clone a graph with DFS?
  • Problem: deep copy a connected undirected graph given one node.
  • Data structure: hash map copies from original node to copy.
  • DFS: if a node is in the map, return its copy; otherwise create the copy, store it, then recursively clone each neighbour and append it.
  • Why store first: a cycle leading back finds the copy and stops.
  • Complexity: O(V + E) time, O(V) space.
  • Example: the square 1-2-3-4-1 produces 4 new nodes and 8 neighbour entries.
Why do we need a hash map in clone graph?

The graph can have cycles and shared neighbours. The map records which nodes already have a copy, so the traversal stops at them and every edge points at the single copy of its node.