Back to HashMap & HashSet Problems
Q6Question 6 of 17

Clone Graph: BFS/DFS with HashMap old→new

Key Concepts
  • Keep a HashMap from each original node to its clone, serving as both a visited set and a clone lookup.
  • Create the clone and insert it into the map before expanding neighbors to prevent infinite recursion on cycles.
  • Wire each neighbor by getting or creating its clone through the map, reproducing the graph structure exactly.
  • Both DFS (recursive) and BFS (queue-based) work, both using the map as the single source of truth.
  • Time is O(V + E) because each node and edge is processed once; space is O(V) for the map and the cloned graph.
  • For an empty graph (null input), return null immediately.

Definition

Traverse the graph keeping a HashMap from original to clone; create each clone once and wire neighbors through the map to handle cycles and shared nodes.
  • Clone Graph – deep-copy every node in a connected undirected graph so the clone has the same structure but shares no objects with the original.
  • HashMap old-to-new – maps each original node to its clone, doubling as a visited set and a clone registry.
  • Insert before expand – create the clone and store it in the map before processing neighbors to break cycles.
  • Traversal – DFS (recursive or stack-based) or BFS (queue-based), both using the map for termination.
  • Complexity – O(V + E) time, O(V) space.

Think of duplicating a network of interlinked bank accounts, where each account points to the accounts it can transfer to. You copy each account exactly once and keep a ledger mapping each original to its copy. As you follow links, you check the ledger: if a copy already exists you link to it, otherwise you make the copy first. This prevents infinite loops when accounts link back to each other.

Understanding the Concepts

What is the Clone Graph problem?

Given a reference to a node in a connected undirected graph, return a deep copy of the entire graph. Each node has a value (int) and a list of neighbors. The clone must have the same structure but share no objects with the original. Cycles in the graph must be handled correctly.

JAVA
package com.bank.model;

import java.util.ArrayList;
import java.util.List;

public class AccountNode {
    public int val;
    public List<AccountNode> neighbors;

    public AccountNode(int val) {
        this.val = val;
        this.neighbors = new ArrayList<>();
    }
}

How does the DFS approach work?

Use a HashMap from original node to its clone. When visiting a node, first check if it is already in the map (already cloned). If so, return the existing clone. Otherwise, create the clone, insert it into the map, then recursively clone each neighbor and add the cloned neighbors to the clone's neighbor list.

JAVA
package com.bank.service;

import com.bank.model.AccountNode;
import java.util.HashMap;
import java.util.Map;

public class GraphCloner {
    private final Map<AccountNode, AccountNode> clones = new HashMap<>();

    public AccountNode cloneGraph(AccountNode node) {
        if (node == null) return null;
        if (clones.containsKey(node)) return clones.get(node);

        AccountNode copy = new AccountNode(node.val);
        clones.put(node, copy); // insert BEFORE expanding neighbors

        for (AccountNode neighbor : node.neighbors) {
            copy.neighbors.add(cloneGraph(neighbor));
        }
        return copy;
    }
}

How does the BFS approach work?

Use a queue for BFS and the same HashMap for clone tracking. Start by cloning the root node and enqueuing it. For each dequeued original node, iterate its neighbors: if a neighbor has not been cloned, clone it, store the mapping, and enqueue it. Then wire the clone's neighbor list by looking up each neighbor's clone in the map.

JAVA
package com.bank.service;

import com.bank.model.AccountNode;
import java.util.*;

public class BfsGraphCloner {
    public AccountNode cloneGraph(AccountNode node) {
        if (node == null) return null;

        Map<AccountNode, AccountNode> clones = new HashMap<>();
        Queue<AccountNode> queue = new LinkedList<>();

        clones.put(node, new AccountNode(node.val));
        queue.add(node);

        while (!queue.isEmpty()) {
            AccountNode original = queue.poll();
            AccountNode copy = clones.get(original);

            for (AccountNode neighbor : original.neighbors) {
                if (!clones.containsKey(neighbor)) {
                    clones.put(neighbor, new AccountNode(neighbor.val));
                    queue.add(neighbor);
                }
                copy.neighbors.add(clones.get(neighbor));
            }
        }
        return clones.get(node);
    }
}

Why must the clone be inserted before expanding neighbors?

If you expanded neighbors first, a neighbor pointing back to the current node would find no clone in the map and recurse infinitely (DFS) or enqueue it again (BFS). Inserting the clone early ensures that any back-reference finds the clone already present, breaking the cycle.

JAVA
// Wrong: expand before insert (infinite recursion on cycles)
AccountNode copy = new AccountNode(node.val);
for (AccountNode nb : node.neighbors) {
    copy.neighbors.add(cloneGraph(nb)); // nb may point back to node
}
clones.put(node, copy); // too late

// Correct: insert before expand
AccountNode copy = new AccountNode(node.val);
clones.put(node, copy); // early insert
for (AccountNode nb : node.neighbors) {
    copy.neighbors.add(cloneGraph(nb)); // back-ref finds clone
}

What is the time and space complexity?

Each node is visited once (O(V)) and each edge is processed once (O(E)), so the total time is O(V + E). The space is O(V) for the HashMap plus O(V) for the recursion stack (DFS) or queue (BFS). The cloned graph itself uses O(V + E) space.

TEXT
Time:  O(V + E)  -- each node and edge processed once
Space: O(V)      -- map + stack/queue
Clone: O(V + E)  -- the copy itself

What are edge cases?

Edge cases include a null input (return null), a single node with no neighbors (clone just the one node), and a graph with self-loops (a node that neighbors itself). The map handles self-loops because the node's clone is inserted before its neighbor list is wired.

JAVA
// Single node, no neighbors
AccountNode single = new AccountNode(1);
AccountNode cloned = new GraphCloner().cloneGraph(single);
System.out.println(cloned.val);              // 1
System.out.println(cloned.neighbors.size()); // 0
System.out.println(cloned != single);        // true (different object)

What are common mistakes?

The most common mistake is not inserting the clone into the map before recursing, which causes infinite loops on cyclic graphs. Another is using a visited boolean instead of a map, which means you cannot look up the clone of a previously visited node to wire its neighbors.

JAVA
// Wrong: using a visited set instead of a map
Set<AccountNode> visited = new HashSet<>();
// Cannot look up the clone of a visited node

// Correct: map serves as both visited set and clone registry
Map<AccountNode, AccountNode> clones = new HashMap<>();

How is this different from cloning a linked list with random pointers?

Both problems use a map from original to clone to handle non-tree pointers. The difference is that Clone Graph handles an arbitrary undirected graph with cycles, while Copy List with Random Pointer handles a linear list with extra forward/backward pointers. The map-based technique is the same, but Clone Graph requires a full traversal (DFS/BFS) while the linked list can be walked linearly.

What should a senior engineer know?

A senior engineer knows that the map is the single source of truth for what has been cloned, that insert-before-expand is the cycle-safety invariant, and that DFS and BFS are interchangeable here with the same complexity. They also mention that for very deep graphs, BFS avoids stack overflow risks that DFS with recursion has.

Visual Overview

DIAGRAM
  Original Graph:
  ┌───┐     ┌───┐
  │ 1 │────│ 2 │
  └─┬─┘     └─┬─┘
    │         │
  ┌─┴─┐     ┌─┴─┐
  │ 4 │────│ 3 │
  └───┘     └───┘

  DFS Clone Process:
  visit(1): create clone(1), map={1:1'}
    visit(2): create clone(2), map={1:1', 2:2'}
      visit(3): create clone(3), map={1:1', 2:2', 3:3'}
        visit(4): create clone(4), map={1:1', 2:2', 3:3', 4:4'}
          neighbor 1: already in map --> use 1'
          neighbor 3: already in map --> use 3'
        neighbor 2: already in map --> use 2'
      neighbor 1: already in map --> use 1'

  Cloned Graph (independent objects):
  ┌────┐     ┌────┐
  │ 1' │────│ 2' │
  └─┬──┘     └─┬──┘
    │          │
  ┌─┴──┐     ┌─┴──┐
  │ 4' │────│ 3' │
  └────┘     └────┘

Key Takeaway

Clone Graph asks for a deep copy of a connected undirected graph given a starting node. My solution traverses the graph, depth-first or breadth-first, keeping a HashMap from each original node to its clone. That map serves double duty: it is both the visited set and the lookup for clones I have already created.

The crucial ordering is that when I first see a node, I create its clone and put it in the map before I expand its neighbors. If I expanded neighbors first, a neighbor pointing back to this node would find no clone yet and recurse forever, so inserting the clone early is exactly what makes cycles terminate. Then for each neighbor I get or create its clone through the map and add it to the current clone's neighbor list.

The cost is O(V + E) because each node and edge is processed once, with O(V) space for the map and the recursion stack or queue. What interviewers check is that I insert the clone into the map before recursing and that I use the map as the single source of truth.

In my project at National Bank of India, I deep-copied a linked-account graph into a sandbox for what-if analysis without touching the live graph. Because accounts could link back to one another forming cycles, the original-to-clone map was essential: it let me copy each account exactly once and reproduce every link, including the cycles, giving analysts an independent graph to experiment on safely.

Remember one sentence: use a HashMap from original to clone, insert the clone before expanding neighbors, and the map handles cycles, shared nodes, and lookup in one structure.

---

Share
Use ← → arrow keys to navigate