Clone Graph
MedPrompt
Given a reference of a node in a connected undirected graph.
Return a deep copy (clone) of the graph.
Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors.
class Node {
public int val;
public List<Node> neighbors;
}
Test case format:
For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with val == 1, the second node with val == 2, and so on. The graph is represented in the test case using an adjacency list.
An adjacency list is a collection of unordered lists used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.
The given node will always be the first node with val = 1. You must return the copy of the given node as a reference to the cloned graph.
Example 1:
Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
Explanation: There are 4 nodes in the graph.
1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).Example 2:
Input: adjList = [[]]
Output: [[]]
Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.Example 3:
Input: adjList = []
Output: []
Explanation: This an empty graph, it does not have any nodes.
Constraints:
- The number of nodes in the graph is in the range
[0, 100]. 1 <= Node.val <= 100Node.valis 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.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a recursive Depth-First Search (DFS) traversal to clone the graph. A hash map is used to store the mapping between original nodes and their newly created clones. This map is crucial to handle cycles and prevent re-cloning the same node multiple times.
Algorithm
-
- Create a hash map
visitedto store the mapping from original nodes to their clones.
- Create a hash map
-
- Define a recursive function
clone(node):
- Define a recursive function
- a. If
nodeis null, return null. - b. If
visitedcontainsnode, returnvisited.get(node). - c. Create a new node
cloneNodewithnode.val. - d. Add the mapping
(node, cloneNode)tovisited. - e. For each
neighborinnode.neighbors: -
i. Add the result of `clone(neighbor)` to `cloneNode.neighbors`. - f. Return
cloneNode. -
- The main function
cloneGraphsimply callsclone(node).
- The main function
Walkthrough
We start traversing the graph from the given node. We use a hash map, say visited, to keep track of nodes that have already been cloned. The map stores the original node as the key and its clone as the value. The core of the approach is a recursive function.
In the recursive function, for a given node:
- If the
nodeis already in ourvisitedmap, it means we have already created a copy for it. We simply return the copy from the map. - If the
nodeis not in the map, we create a newNodewith the same value. - We immediately put this new node into the
visitedmap, with the originalnodeas the key. This is important to do before traversing its neighbors to handle cycles correctly. - Then, we iterate through each
neighborof the originalnode. - For each
neighbor, we make a recursive call. This call will return the cloned neighbor node (either by creating it or fetching it from the map). - We add this returned cloned neighbor to the neighbors list of our newly created node.
- Finally, we return the new node.
The initial call to cloneGraph will just call this recursive helper function starting with the given node.
/*// 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; }}*/ class Solution { private HashMap<Node, Node> visited = new HashMap<>(); public Node cloneGraph(Node node) { if (node == null) { return node; } // If the node was already visited, return the clone from the map. if (visited.containsKey(node)) { return visited.get(node); } // Create a new node with the same value as the old node. Node cloneNode = new Node(node.val, new ArrayList<>()); // Add the new node to the visited map. // This must be done before visiting the neighbors to prevent infinite loops. visited.put(node, cloneNode); // Iterate through the neighbors of the original node and recursively clone them. for (Node neighbor : node.neighbors) { cloneNode.neighbors.add(cloneGraph(neighbor)); } return cloneNode; }}Complexity
Time
O(N + E), where N is the number of nodes and E is the number of edges. Each node is visited once, and we iterate through all its edges.
Space
O(N), for the hash map which stores all N nodes, and for the recursion stack which can go up to N deep in the worst case (for a skewed graph).
Trade-offs
Pros
Conceptually simple and often leads to more concise code.
Efficient, as it visits each node and edge only once.
Cons
For very deep graphs, the recursion could lead to a stack overflow error. (Though not an issue with the given constraints).
This approach uses an iterative Breadth-First Search (BFS) traversal. It uses a queue to manage the nodes to visit and a hash map to store the mapping from original nodes to their clones, just like the DFS approach. This avoids recursion and the potential for stack overflow.
Algorithm
-
- If the input
nodeis null, return null.
- If the input
-
- Create a
Queuefor BFS and add the startingnode.
- Create a
-
- Create a
HashMap<Node, Node>calledvisitedto map original nodes to their clones.
- Create a
-
- Create the clone for the starting
node, and put the mapping invisited.
- Create the clone for the starting
-
- While the
queueis not empty:
- While the
- a. Dequeue a node,
originalNode. - b. For each
neighboroforiginalNode: -
i. If `neighbor` is not in `visited`: -
- Create a clone for `neighbor` and add it to `visited`. -
- Enqueue the `neighbor`. -
ii. Add the cloned neighbor (`visited.get(neighbor)`) to the neighbor list of the cloned `originalNode` (`visited.get(originalNode).neighbors`). -
- Return the clone of the starting
nodefrom thevisitedmap.
- Return the clone of the starting
Walkthrough
The core idea is to create a copy of a node, then add its neighbors to a queue to be processed later. We use a hash map to ensure we don't create duplicate clones for the same node.
The algorithm proceeds as follows:
- Handle the edge case where the input
nodeis null. - Create a queue and add the starting
nodeto it. - Create a hash map
visitedto storeoriginal_node -> cloned_nodemappings. - Create the clone for the starting
nodeand put it in thevisitedmap. - While the queue is not empty:
a. Dequeue a node, let's call it
originalNode. b. Iterate through allneighbors oforiginalNode. c. For eachneighbor: i. If theneighboris not in thevisitedmap, it means we haven't seen it before. So, we create a clone for it, add it to thevisitedmap, and enqueue the originalneighborfor later processing. d. After checking all neighbors, we connect the clonedoriginalNodeto its cloned neighbors. We retrieve the clone oforiginalNodefrom the map and add the clones of its neighbors (also retrieved from the map) to its neighbor list.
/*// 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; }}*/ class Solution { public Node cloneGraph(Node node) { if (node == null) { return null; } HashMap<Node, Node> visited = new HashMap<>(); Queue<Node> queue = new LinkedList<>(); // Clone the root node and add it to the queue and visited map. visited.put(node, new Node(node.val, new ArrayList<>())); queue.add(node); // Start BFS traversal while (!queue.isEmpty()) { // Get the original node from the front of the queue. Node originalNode = queue.poll(); // Iterate through the neighbors of the original node. for (Node neighbor : originalNode.neighbors) { // If the neighbor hasn't been cloned yet. if (!visited.containsKey(neighbor)) { // Clone it and add to the map. visited.put(neighbor, new Node(neighbor.val, new ArrayList<>())); // Add the original neighbor to the queue for processing. queue.add(neighbor); } // Add the cloned neighbor to the neighbors list of the cloned current node. visited.get(originalNode).neighbors.add(visited.get(neighbor)); } } return visited.get(node); }}Complexity
Time
O(N + E), where N is the number of nodes and E is the number of edges. Each node is enqueued and dequeued once, and we iterate through all its edges.
Space
O(N), for the hash map and the queue. The queue can hold up to O(W) nodes where W is the maximum width of the graph, which can be O(N) in the worst case.
Trade-offs
Pros
Iterative approach avoids recursion, preventing potential stack overflow errors on very large or deep graphs.
Efficient, as it visits each node and edge only once.
Cons
The code can be slightly more verbose than the recursive DFS version.
Solutions
Solution
/** * // Definition for a _Node. * function _Node(val, neighbors) { * this.val = val === undefined ? 0 : val; * this.neighbors = neighbors === undefined ? [] : neighbors; * }; */ /** * @param {_Node} node * @return {_Node} */ var cloneGraph = function (node) { const g = new Map(); const dfs = (node) => { if (!node) { return null; } if (g.has(node)) { return g.get(node); } const cloned = new _Node(node.val); g.set(node, cloned); for (const nxt of node.neighbors) { cloned.neighbors.push(dfs(nxt)); } return cloned; }; return dfs(node); };using System.Collections.Generic ; public class Solution { public Node CloneGraph ( Node node ) { if ( node == null ) return null ; var dict = new Dictionary < int , Node >(); var queue = new Queue < Node >(); queue . Enqueue ( CloneVal ( node )); dict . Add ( node . val , queue . Peek ()); while ( queue . Count > 0 ) { var current = queue . Dequeue (); var newNeighbors = new List < Node >( current . neighbors . Count ); foreach ( var oldNeighbor in current . neighbors ) { Node newNeighbor ; if (! dict . TryGetValue ( oldNeighbor . val , out newNeighbor )) { newNeighbor = CloneVal ( oldNeighbor ); queue . Enqueue ( newNeighbor ); dict . Add ( newNeighbor . val , newNeighbor ); } newNeighbors . Add ( newNeighbor ); } current . neighbors = newNeighbors ; } return dict [ node . val ]; } private Node CloneVal ( Node node ) { return new Node ( node . val , new List < Node >( node . neighbors )); } }/* // 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; } } */ class Solution { private Map < Node , Node > visited = new HashMap <>(); public Node cloneGraph ( Node node ) { if ( node == null ) { return null ; } if ( visited . containsKey ( node )) { return visited . get ( node ); } Node clone = new Node ( node . val ); visited . put ( node , clone ); for ( Node e : node . neighbors ) { clone . neighbors . add ( cloneGraph ( e )); } return clone ; } }""" # 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 : 'Node' ) -> 'Node' : visited = defaultdict () def clone ( node ): if node is None : return None if node in visited : return visited [ node ] c = Node ( node . val ) visited [ node ] = c for e in node . neighbors : c . neighbors . append ( clone ( e )) return c return clone ( node )/* // Definition for a Node. class Node { public: int val; vector<Node*> neighbors; Node() { val = 0; neighbors = vector<Node*>(); } Node(int _val) { val = _val; neighbors = vector<Node*>(); } Node(int _val, vector<Node*> _neighbors) { val = _val; neighbors = _neighbors; } }; */ class Solution { public: unordered_map < Node * , Node *> visited ; Node * cloneGraph ( Node * node ) { if ( ! node ) return nullptr ; if ( visited . count ( node )) return visited [ node ]; Node * clone = new Node ( node -> val ); visited [ node ] = clone ; for ( auto & e : node -> neighbors ) clone -> neighbors . push_back ( cloneGraph ( e )); return clone ; } };Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.