Clone Undirected Graph
Problem
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;
}
OR
class UndirectedGraphNode {
int val;
List<UndirectedGraphNode> neighbors;
UndirectedGraphNode(int x) {
val = x;
neighbors = new ArrayList<UndirectedGraphNode> ();
}
};
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.
Examples
Example 1:
graph LR subgraph Original A1[1]:::orig B1[2]:::orig C1[3]:::orig D1[4]:::orig A1 --- B1 & D1 B1 --- C1 D1 --- C1 end subgraph Same["Don't return same graph"] A2[1]:::orig B2[2]:::orig C2[3]:::orig D2[4]:::orig A2 --- B2 & D2 B2 --- C2 D2 --- C2 end subgraph Correct["Looks same + new nodes"] A3[1]:::corr B3[2]:::corr C3[3]:::corr D3[4]:::corr A3 --- B3 & D3 B3 --- C3 D3 --- C3 end subgraph Messy["Wrong connections"] A4[1]:::mess B4[3]:::mess C4[2]:::mess D4[4]:::mess A4 --- B4 & D4 B4 --- C4 D4 --- C4 end Original --x Same Original --> Correct Original --x Messy classDef orig stroke:#FF8C00,stroke-width:2px classDef corr stroke:blue,stroke-width:2px classDef mess stroke:#f66,stroke-width:2px
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:
graph LR; A(1)
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.
Solution
A graph clone requires creating a copy of each node and appropriately linking its neighbours in the clone while ensuring each node is cloned only once. So, we can use DFS or BFS to traverse through the graph starting from the given node.
Video explanation
Here is the video explaining below methods in detail. Please check it out:
<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/UnPCxesy6sE" frameborder="0" allowfullscreen></iframe></div>
Method 1 - DFS
Intuition
The core idea is that to clone a graph, we must create a new node for each original node and ensure all neighbor relationships are preserved. Since graphs may contain cycles, we need to avoid infinite loops and duplicate nodes by keeping track of already-cloned nodes. DFS allows us to traverse the graph deeply, cloning nodes and their neighbors recursively, while a hashmap ensures each node is cloned only once.
Approach
- If the input node is
null, returnnull. - Use a hashmap to map each original node to its clone.
- For each node, if it has already been cloned (exists in the hashmap), return the clone.
- Otherwise, create a new node, add it to the hashmap, and recursively clone all its neighbors, adding them to the clone's neighbor list.
- Return the clone of the starting node.
Approach
- Mapping
- Maintain a hashmap (or dictionary)
visitedthat maps each original node to its clone. - If a node has already been cloned, use the clone from the hashmap.
- If not, create a new clone and add it to the hashmap.
- Maintain a hashmap (or dictionary)
- Build the clone
- For each node, recursively (or iteratively) visit its neighbours and add the clones' neighbours accordingly.
We can use origToCopyMap containing node and cloned node.
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;
}
}
*/
class Solution {
// HashMap to store the mapping from original nodes to their cloned counterparts.
// This helps in avoiding cycles and reusing cloned nodes.
private HashMap<Node, Node> oldToNewMap = new HashMap<>();
public Node cloneGraph(Node node) {
if (node == null) {
return null;
}
// If the node has already been cloned, return its clone from the map.
if (oldToNewMap.containsKey(node)) {
return oldToNewMap.get(node);
}
// Create a new node for the current original node.
Node clonedNode = new Node(node.val, new ArrayList<>());
// Add the mapping to the HashMap immediately to handle cycles.
oldToNewMap.put(node, clonedNode);
// Recursively clone the neighbors and add them to the cloned node's neighbors list.
for (Node neighbor : node.neighbors) {
clonedNode.neighbors.add(cloneGraph(neighbor));
}
return clonedNode;
}
}
Python
"""
# 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 []
"""
from typing import Optional, List
class Solution:
def __init__(self):
# HashMap to store the mapping from original nodes to their cloned counterparts.
# This helps in avoiding cycles and reusing cloned nodes.
self.old_to_new_map = {}
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
if not node:
return None
# If the node has already been cloned, return its clone from the map.
if node in self.old_to_new_map:
return self.old_to_new_map[node]
# Create a new node for the current original node.
cloned_node = Node(node.val)
# Add the mapping to the HashMap immediately to handle cycles.
self.old_to_new_map[node] = cloned_node
# Recursively clone the neighbors and add them to the cloned node's neighbors list.
for neighbor in node.neighbors:
cloned_node.neighbors.append(self.cloneGraph(neighbor))
return cloned_node
C++
class Solution {
public:
Node* cloneGraph(Node* node) {
if (!node) return nullptr;
unordered_map<Node*, Node*> map;
return dfs(node, map);
}
Node* dfs(Node* node, unordered_map<Node*, Node*>& map) {
if (map.count(node)) return map[node];
Node* clone = new Node(node->val);
map[node] = clone;
for (auto neighbor : node->neighbors) {
clone->neighbors.push_back(dfs(neighbor, map));
}
return clone;
}
};
Go
func cloneGraph(node *Node) *Node {
if node == nil {
return nil
}
visited := make(map[*Node]*Node)
var dfs func(*Node) *Node
dfs = func(n *Node) *Node {
if v, ok := visited[n]; ok {
return v
}
clone := &Node{Val: n.Val}
visited[n] = clone
for _, neighbor := range n.Neighbors {
clone.Neighbors = append(clone.Neighbors, dfs(neighbor))
}
return clone
}
return dfs(node)
}
Kotlin
class Solution {
fun cloneGraph(node: Node?): Node? {
if (node == null) return null
val visited = mutableMapOf<Node, Node>()
fun dfs(n: Node): Node {
if (visited.containsKey(n)) return visited[n]!!
val clone = Node(n.`val`)
visited[n] = clone
for (neighbor in n.neighbors) {
clone.neighbors.add(dfs(neighbor))
}
return clone
}
return dfs(node)
}
}
Rust
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
pub fn clone_graph(node: Option<Rc<RefCell<Node>>>) -> Option<Rc<RefCell<Node>>> {
fn dfs(
node: &Option<Rc<RefCell<Node>>>,
visited: &mut HashMap<i32, Rc<RefCell<Node>>>,
) -> Option<Rc<RefCell<Node>>> {
if let Some(n) = node {
let val = n.borrow().val;
if let Some(clone) = visited.get(&val) {
return Some(Rc::clone(clone));
}
let new_node = Rc::new(RefCell::new(Node {
val,
neighbors: vec![],
}));
visited.insert(val, Rc::clone(&new_node));
for neighbor in &n.borrow().neighbors {
new_node.borrow_mut().neighbors.push(dfs(&Some(Rc::clone(neighbor)), visited).unwrap());
}
Some(new_node)
} else {
None
}
}
dfs(&node, &mut HashMap::new())
}
}
Typescript
function cloneGraph(node: Node | null): Node | null {
if (!node) return null;
const visited = new Map<Node, Node>();
function dfs(n: Node): Node {
if (visited.has(n)) return visited.get(n)!;
const clone = new Node(n.val);
visited.set(n, clone);
for (const neighbor of n.neighbors) {
clone.neighbors.push(dfs(neighbor));
}
return clone;
}
return dfs(node);
}
Complexity
- ⏰ Time complexity:
O(V + E)whereVis the number of vertices (nodes) andEis the number of edges, since we visit each node and edge once. - 🧺 Space complexity:
O(V)for the hashmap and the recursion/queue stack, whereVis the number of nodes.
Method 2 - BFS
Intuition
Instead of recursion, we can use BFS to systematically visit each node in the graph layer by layer. By using a queue and a hashmap to track already-cloned nodes, we ensure that each node is cloned exactly once and all neighbor relationships are correctly established. BFS is especially useful for wide graphs and avoids recursion stack overflow.
Approach
- If the input node is
null, returnnull. - Use a hashmap to map each original node to its clone.
- Initialize a queue and enqueue the starting node, cloning it and adding it to the hashmap.
- While the queue is not empty:
- Dequeue a node and iterate through its neighbors.
- For each neighbor, if it hasn't been cloned, clone it, add it to the hashmap, and enqueue it.
- Add the neighbor's clone to the current node's clone's neighbor list.
- Return the clone of the starting node.
We can use BFS by:
- Using a queue, we systematically visit each node layer by layer.
- We create a clone for each node and link its neighbours as we traverse.
Algorithm
- If the input node is
null, returnnull. - Initialise a mapping (
visited) that stores the reference to already cloned nodes. - Use a queue to traverse the graph.
- For each node:
- Clone the node if it hasn’t been cloned already (using
visitedas a check). - Traverse its neighbours, clone them (if necessary), and connect the current node’s clone to its neighbours' clones.
- Add unvisited neighbours to the queue.
- Clone the node if it hasn’t been cloned already (using
- Once all nodes are processed, the graph is fully cloned.
Code
Java
// Node is undirected graph
public Node cloneGraph(Node node) {
if (node == null)
return null;
Queue<Node> queue = new LinkedList<Node> ();
Map<Node, Node> origToCopyMap = new HashMap<Node, Node> ();
Node newHead = new Node(node.val);
queue.add(node);
map.put(node, newHead);
while (!queue.isEmpty()) {
Node curr = queue.pop();
for (Node adjNode: curr.neighbors) {
if (!origToCopyMap.containsKey(adjNode)) {
Node copy = new Node(adjNode.val);
origToCopyMap.put(adjNode, copy);
origToCopyMap.get(curr).neighbors.add(copy);
queue.add(adjNode);
} else {
origToCopyMap.get(curr)
.neighbors.add(map.get(adjNode));
}
}
}
return newHead;
}
Python
from collections import deque
from typing import Optional
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
if not node:
return None
visited = {}
queue = deque([node])
visited[node] = Node(node.val)
while queue:
curr = queue.popleft()
for neighbor in curr.neighbors:
if neighbor not in visited:
visited[neighbor] = Node(neighbor.val)
queue.append(neighbor)
visited[curr].neighbors.append(visited[neighbor])
return visited[node]
C++
class Solution {
public:
Node* cloneGraph(Node* node) {
if (!node) return nullptr;
unordered_map<Node*, Node*> map;
queue<Node*> q;
q.push(node);
map[node] = new Node(node->val);
while (!q.empty()) {
Node* curr = q.front(); q.pop();
for (auto neighbor : curr->neighbors) {
if (!map.count(neighbor)) {
map[neighbor] = new Node(neighbor->val);
q.push(neighbor);
}
map[curr]->neighbors.push_back(map[neighbor]);
}
}
return map[node];
}
};
Go
func cloneGraph(node *Node) *Node {
if node == nil {
return nil
}
visited := make(map[*Node]*Node)
queue := []*Node{node}
visited[node] = &Node{Val: node.Val}
for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]
for _, neighbor := range curr.Neighbors {
if _, ok := visited[neighbor]; !ok {
visited[neighbor] = &Node{Val: neighbor.Val}
queue = append(queue, neighbor)
}
visited[curr].Neighbors = append(visited[curr].Neighbors, visited[neighbor])
}
}
return visited[node]
}
Kotlin
class Solution {
fun cloneGraph(node: Node?): Node? {
if (node == null) return null
val visited = mutableMapOf<Node, Node>()
val queue = ArrayDeque<Node>()
queue.add(node)
visited[node] = Node(node.`val`)
while (queue.isNotEmpty()) {
val curr = queue.removeFirst()
for (neighbor in curr.neighbors) {
if (!visited.containsKey(neighbor)) {
visited[neighbor] = Node(neighbor.`val`)
queue.add(neighbor)
}
visited[curr]?.neighbors?.add(visited[neighbor]!!)
}
}
return visited[node]
}
}
Rust
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
pub fn clone_graph(node: Option<Rc<RefCell<Node>>>) -> Option<Rc<RefCell<Node>>> {
if node.is_none() {
return None;
}
let node = node.unwrap();
let mut visited = HashMap::new();
let mut queue = VecDeque::new();
queue.push_back(Rc::clone(&node));
visited.insert(node.borrow().val, Rc::new(RefCell::new(Node {
val: node.borrow().val,
neighbors: vec![],
})));
while let Some(curr) = queue.pop_front() {
let curr_val = curr.borrow().val;
for neighbor in &curr.borrow().neighbors {
let neighbor_val = neighbor.borrow().val;
if !visited.contains_key(&neighbor_val) {
visited.insert(neighbor_val, Rc::new(RefCell::new(Node {
val: neighbor_val,
neighbors: vec![],
})));
queue.push_back(Rc::clone(neighbor));
}
visited.get(&curr_val).unwrap().borrow_mut().neighbors.push(
visited.get(&neighbor_val).unwrap().clone()
);
}
}
visited.get(&node.borrow().val).cloned()
}
}
Typescript
function cloneGraph(node: Node | null): Node | null {
if (!node) return null;
const visited = new Map<Node, Node>();
const queue: Node[] = [node];
visited.set(node, new Node(node.val));
while (queue.length > 0) {
const curr = queue.shift()!;
for (const neighbor of curr.neighbors) {
if (!visited.has(neighbor)) {
visited.set(neighbor, new Node(neighbor.val));
queue.push(neighbor);
}
visited.get(curr)!.neighbors.push(visited.get(neighbor)!);
}
}
return visited.get(node)!;
}
Complexity
- ⏰ Time complexity:
O(V+E), because BFS ensures each node and edge is visited once. - 🧺 Space complexity:
O(V)for the hashmap and the BFS queue.