150NeetCode 150

81.Clone Graph

MediumBlind 75Graphs ยท 2/19

Problem

You are given a reference to one node of a connected, undirected graph. Return a deep copy (clone) of the whole graph.

Each node has an integer val and a list of neighbors. The clone must consist of brand-new Node objects with the same values and the same neighbour structure โ€” no node in the copy may point to a node of the original graph.

For testing, node values are 1..n and the graph is given as an adjacency list where index i lists the neighbours of node i + 1. The given node is always node 1 (or null for an empty graph).

Examples

Input:  adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
Explanation: 4 nodes in a square: 1-2, 2-3, 3-4, 4-1. The output has the same shape but every node is a new object.
Input:  adjList = [[]]
Output: [[]]
Explanation: A single node with no neighbours.
Input:  adjList = []
Output: []
Explanation: Empty graph โ€” the input node is null.

Constraints

  • The number of nodes is in the range [0, 100]
  • 1 <= Node.val <= 100, and every Node.val is unique
  • No repeated edges and no self-loops
  • The graph is connected, so every node is reachable from the given node

๐Ÿ’ก Key Insight

The graph has cycles (an undirected edge 1-2 means 1 lists 2 and 2 lists 1), so naive recursion would loop forever. Keep a map original โ†’ clone. The map doubles as the visited set: before cloning a node, check if its copy already exists and reuse it. Registering the clone in the map before visiting its neighbours is what breaks cycles. With O(1) hash lookups, every node and edge is processed once.

Brute Force (Two Passes with List Lookup)

TimeO(Vยท(V + E))SpaceO(V)

Intuition

Split the job in two: first discover every node, then build copies and wire their edges. If we keep the originals in a List and the copies in a parallel List at the same indices, the copy of any original is copies.get(originals.indexOf(original)). That lookup is a linear scan, which is what makes this the slow version.

Algorithm

  1. If node == null, return null.
  2. BFS from node, appending each newly discovered node to originals (contains is used as the visited check).
  3. Create copies.get(i) = new Node(originals.get(i).val) for every index.
  4. For each original i and each neighbour nb, append copies.get(originals.indexOf(nb)) to copies.get(i).neighbors.
  5. Return copies.get(0).

Visualization

originals = [n1, n2, n4, n3]     (BFS order from node 1)
copies    = [c1, c2, c4, c3]     (same indices)

wire n1.neighbors = [n2, n4] โ†’ indexOf = 1, 2 โ†’ c1.neighbors = [c2, c4]
wire n2.neighbors = [n1, n3] โ†’ indexOf = 0, 3 โ†’ c2.neighbors = [c1, c3]
...each indexOf is a linear scan of originals

Code

java
/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> neighbors;
    public Node() {
        val = 0;
        neighbors = new ArrayList<Node>();
    }
    public Node(int _val) {
        val = _val;
        neighbors = new ArrayList<Node>();
    }
    public Node(int _val, ArrayList<Node> _neighbors) {
        val = _val;
        neighbors = _neighbors;
    }
}
*/

import java.util.*;

class Solution {
    public Node cloneGraph(Node node) {
        if (node == null) return null;

        // Pass 1: discover every node (BFS); contains() is an O(V) scan
        List<Node> originals = new ArrayList<>();
        Deque<Node> queue = new ArrayDeque<>();
        originals.add(node);
        queue.offer(node);
        while (!queue.isEmpty()) {
            Node cur = queue.poll();
            for (Node nb : cur.neighbors) {
                if (!originals.contains(nb)) {
                    originals.add(nb);
                    queue.offer(nb);
                }
            }
        }

        // Pass 2: one copy per original, at the same index
        List<Node> copies = new ArrayList<>();
        for (Node orig : originals) {
            copies.add(new Node(orig.val));
        }

        // Pass 3: wire edges by locating each neighbour's index
        for (int i = 0; i < originals.size(); i++) {
            for (Node nb : originals.get(i).neighbors) {
                copies.get(i).neighbors.add(copies.get(originals.indexOf(nb)));
            }
        }
        return copies.get(0);
    }
}

Complexity

  • Time: O(Vยท(V + E)) โ€” every visited-check and every edge lookup is a linear contains / indexOf over V nodes.
  • Space: O(V) โ€” the two lists and the BFS queue.

Complexity Comparison

Approach Time Space
Brute Force (List Lookup) O(Vยท(V + E)) O(V)
DFS + HashMap O(V + E) O(V)
BFS + HashMap O(V + E) O(V)

Common Pitfalls

  • Putting the clone into the map after recursing into neighbours โ€” the cycle 1 โ†’ 2 โ†’ 1 then recurses forever (StackOverflowError).
  • Returning the original node (or adding original neighbours to the clone) โ€” the result must contain only new objects.
  • Forgetting the null input case for an empty graph.
  • Marking visited by val in a boolean[] but still creating a second Node for the same value โ€” always reuse the stored clone.
  • In BFS, enqueuing a neighbour every time it is seen instead of only when its clone is first created, which duplicates edges in the copy.