133. Clone Graph - Explanation

Problem Link

Description

Given a node in a connected undirected graph, return a deep copy of the graph.

Each node in the graph contains an integer value and a list of its neighbors.

class Node {
    public int val;
    public List<Node> neighbors;
}

The graph is shown in the test cases as an adjacency list. An adjacency list is a mapping of nodes to lists, used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.

For simplicity, nodes values are numbered from 1 to n, where n is the total number of nodes in the graph. The index of each node within the adjacency list is the same as the node's value (1-indexed).

The input node will always be the first node in the graph and have 1 as the value.

Example 1:

Input: adjList = [[2],[1,3],[2]]

Output: [[2],[1,3],[2]]

Explanation: There are 3 nodes in the graph.
Node 1: val = 1 and neighbors = [2].
Node 2: val = 2 and neighbors = [1, 3].
Node 3: val = 3 and neighbors = [2].

Example 2:

Input: adjList = [[]]

Output: [[]]

Explanation: The graph has one node with no neighbors.

Example 3:

Input: adjList = []

Output: []

Explanation: The graph is empty.

Constraints:

  • 0 <= The number of nodes in the graph <= 100.
  • 1 <= Node.val <= 100
  • There are no duplicate edges and no self-loops in the graph.


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(V + E) time and O(E) space, where V is the number of vertices and E is the number of edges in the given graph.


Hint 1

We are given only the reference to the node in the graph. Cloning the entire graph means we need to clone all the nodes as well as their child nodes. We can't just clone the node and its neighbor and return the node. We also need to clone the entire graph. Can you think of a recursive way to do this, as we are cloning nodes in a nested manner? Also, can you think of a data structure that can store the nodes with their cloned references?


Hint 2

We can use the Depth First Search (DFS) algorithm. We use a hash map to map the nodes to their cloned nodes. We start from the given node. At each step of the DFS, we create a node with the current node's value. We then recursively go to the current node's neighbors and try to clone them first. After that, we add their cloned node references to the current node's neighbors list. Can you think of a base condition to stop this recursive path?


Hint 3

We stop this recursive path when we encounter a node that has already been cloned or visited. This DFS approach creates an exact clone of the given graph, and we return the clone of the given node.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation - Understanding adjacency lists and how nodes connect to neighbors
  • DFS/BFS Traversal - Traversing all nodes in a graph using depth-first or breadth-first search
  • Hash Maps - Using a map to track visited nodes and prevent infinite loops in cyclic graphs

1. Depth First Seacrh

Intuition

The graph may contain cycles, so we cannot simply copy nodes recursively without remembering what we've already copied.
To handle this, we use a map (old → new):

  • When we first see a node, we create its copy.
  • If we see the same node again, we reuse the already-created copy.
  • This avoids infinite loops and ensures each node is cloned exactly once.

Depth First Search (DFS) helps us explore and clone all connected nodes.

Algorithm

  1. If the input node is null, return null.
  2. Create a map to store original nodes -> cloned nodes.
  3. Start DFS from the given node:
    • If the node is already in the map, return its clone.
    • Create a new node with the same value.
    • Store it in the map.
    • Recursively clone all neighbors and add them to the clone’s neighbor list.
  4. Return the cloned node corresponding to the starting node.
"""
# Definition for a Node.
class Node:
    def __init__(self, val = 0, neighbors = None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []
"""

class Solution:
    def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
        oldToNew = {}

        def dfs(node):
            if node in oldToNew:
                return oldToNew[node]

            copy = Node(node.val)
            oldToNew[node] = copy
            for nei in node.neighbors:
                copy.neighbors.append(dfs(nei))
            return copy

        return dfs(node) if node else None

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V)O(V)

Where VV is the number of vertices and EE is the number of edges.


Intuition

The graph can have cycles, so while cloning we must avoid creating duplicate nodes or looping forever.
Using Breadth First Search (BFS), we explore the graph level by level and keep a map from original nodes to their clones.

  • When a node is first seen, create its clone and store it in the map.
  • For every neighbor, ensure its clone exists, then connect the cloned nodes.
  • The map guarantees each node is cloned only once.

Algorithm

  1. If the input node is null, return null.
  2. Create a map to store original node -> cloned node.
  3. Initialize a queue with the starting node and create its clone.
  4. While the queue is not empty:
    • Pop a node.
    • For each of its neighbors:
      • If the neighbor is not cloned yet, clone it and add it to the queue.
      • Add the cloned neighbor to the current node’s clone neighbor list.
  5. Return the cloned version of the starting node.
"""
# Definition for a Node.
class Node:
    def __init__(self, val = 0, neighbors = None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []
"""

class Solution:
    def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
        if not node:
            return None

        oldToNew = {}
        oldToNew[node] = Node(node.val)
        q = deque([node])

        while q:
            cur = q.popleft()
            for nei in cur.neighbors:
                if nei not in oldToNew:
                    oldToNew[nei] = Node(nei.val)
                    q.append(nei)
                oldToNew[cur].neighbors.append(oldToNew[nei])

        return oldToNew[node]

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V)O(V)

Where VV is the number of vertices and EE is the number of edges.


Common Pitfalls

Forgetting to Handle Cycles

Failing to track visited nodes causes infinite recursion when the graph contains cycles. Always use a map to store already-cloned nodes before processing neighbors.

# Wrong: No visited tracking
def dfs(node):
    copy = Node(node.val)
    for nei in node.neighbors:
        copy.neighbors.append(dfs(nei))  # Infinite loop on cycles
    return copy

Cloning the Same Node Multiple Times

Without a mapping from original to cloned nodes, the same node may be cloned multiple times, breaking the graph structure. Two neighbors pointing to the same original node should point to the same cloned node.

# Wrong: Creates duplicate clones
if nei not in oldToNew:
    oldToNew[nei] = Node(nei.val)
# Must add to map BEFORE recursing to handle back-edges

Adding Clone to Map After Processing Neighbors

The clone must be added to the map immediately after creation, before recursing on neighbors. Otherwise, back-edges to the current node will create a new clone instead of reusing the existing one.