Code โบ algorithm-study
LeetCode 133 - Clone Graph
Deep copying an undirected graph using a hash map and DFS recursion in Python
This Medium graph problem asks for a deep copy of a connected undirected graph given a reference to a single starting node. To handle cycles and duplicate visits, I used a hash map to memoize cloned nodes and traversed the structure via DFS recursion.
Problem link and explanation
- Problem: 133. Clone Graph
- Summary: Given a reference of a node in a connected undirected graph, return a deep copy (clone) of the graph containing identical node values and neighbor structures without sharing any node references with the original graph.
Because undirected graphs can contain cycles, an unmemoized traversal will loop infinitely trying to clone visited neighbors. A 1:1 lookup map between original nodes and their cloned counterparts is necessary to guarantee single-pass node creation.
Approach
Maintain a cloned dictionary mapping each original Node instance to its corresponding copy.
cloned = {}
def dfs(curr):
if curr in cloned:
return cloned[curr]
copy = Node(curr.val)
cloned[curr] = copy
for neighbor in curr.neighbors:
copy.neighbors.append(dfs(neighbor))
return copy
- Base condition: If
curris already present incloned, return the existing clonecloned[curr]immediately. - Clone and register: If
currhas not been cloned yet, instantiatecopy = Node(curr.val)and register it incloned[curr]before recursing on neighbors. This registration order prevents infinite recursion when neighboring nodes referencecurrback. - Populate adjacency list: Iterate over
curr.neighbors, recursively callingdfs(neighbor)for each, and append the returned clone tocopy.neighbors.
Complexity analysis
- Time complexity: O(V + E)
- Every vertex
Vand edgeEis traversed exactly once.
- Every vertex
- Space complexity: O(V)
- The hash map stores all
Vvertices, and the recursion call stack reaches at mostVframes in the worst case.
- The hash map stores all
Implementation code
"""
# 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
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
if not node:
return None
cloned = {}
def dfs(curr):
if curr in cloned:
return cloned[curr]
copy = Node(curr.val)
cloned[curr] = copy
for neighbor in curr.neighbors:
copy.neighbors.append(dfs(neighbor))
return copy
return dfs(node)
Summary and reflection
The key invariant in graph cloning is breaking recursive cycles. Storing the newly created node in the memoization table before recursing into its neighbors guarantees that cyclic references resolve immediately to the cloned node instance rather than triggering unbounded recursion.