Skip to content

Commit 939acd8

Browse files
committed
Clone graph
1 parent 6a7a0d8 commit 939acd8

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

133.clone_graph/clone.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# 133. Clone graph
2+
# Topics: 'Hash Table', 'Depth-First Search', 'Breadth-First Search', 'Graph'
3+
# Level: 'Medium'
4+
5+
# Given a reference of a node in a connected undirected graph.
6+
7+
# Return a deep copy (clone) of the graph.
8+
9+
# Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors.
10+
11+
# class Node {
12+
# public int val;
13+
# public List<Node> neighbors;
14+
# }
15+
16+
17+
# Test case format:
18+
19+
# 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.
20+
21+
# 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.
22+
23+
# 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.
24+
25+
26+
27+
# Example 1:
28+
29+
# Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
30+
# Output: [[2,4],[1,3],[2,4],[1,3]]
31+
# Explanation: There are 4 nodes in the graph.
32+
# 1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
33+
# 2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
34+
# 3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
35+
# 4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
36+
37+
# Example 2:
38+
39+
# Input: adjList = [[]]
40+
# Output: [[]]
41+
# 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.
42+
43+
# Example 3:
44+
45+
# Input: adjList = []
46+
# Output: []
47+
# Explanation: This an empty graph, it does not have any nodes.
48+
49+
50+
51+
# Constraints:
52+
53+
# The number of nodes in the graph is in the range [0, 100].
54+
# 1 <= Node.val <= 100
55+
# Node.val is unique for each node.
56+
# There are no repeated edges and no self-loops in the graph.
57+
# The Graph is connected and all nodes can be visited starting from the given node.
58+
59+
60+
61+
from collections import deque
62+
from typing import Optional
63+
64+
class Node:
65+
def __init__(self, val = 0, neighbors = None):
66+
self.val = val
67+
self.neighbors = neighbors if neighbors is not None else []
68+
69+
class Solution:
70+
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
71+
if not node:
72+
return node
73+
clones = {node.val: Node(node.val)}
74+
q = deque([node])
75+
while q:
76+
cur = q.popleft()
77+
for n in cur.neighbors:
78+
if n.val not in clones:
79+
clones[n.val] = Node(n.val)
80+
q.append(n)
81+
clones[cur.val].neighbors.append(clones[n.val])
82+
83+
return clones[node.val]

133.clone_graph/test_clone.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import unittest
2+
from collections import deque
3+
from clone import Node, Solution
4+
5+
# Assuming Node and Solution are already defined above
6+
7+
class TestCloneGraph(unittest.TestCase):
8+
def build_graph(self, adj):
9+
if not adj:
10+
return None
11+
nodes = {i+1: Node(i+1) for i in range(len(adj))}
12+
for i, neighbors in enumerate(adj, start=1):
13+
nodes[i].neighbors = [nodes[n] for n in neighbors]
14+
return nodes[1]
15+
16+
def graph_to_adj(self, node):
17+
if not node:
18+
return []
19+
adj = {}
20+
q = deque([node])
21+
seen = set()
22+
while q:
23+
cur = q.popleft()
24+
if cur.val in seen:
25+
continue
26+
seen.add(cur.val)
27+
adj[cur.val] = [n.val for n in cur.neighbors]
28+
for n in cur.neighbors:
29+
q.append(n)
30+
return [adj[i] for i in sorted(adj.keys())]
31+
32+
def test_empty_graph(self):
33+
sol = Solution()
34+
self.assertIsNone(sol.cloneGraph(None))
35+
36+
def test_single_node(self):
37+
start = self.build_graph([[]])
38+
sol = Solution()
39+
cloned = sol.cloneGraph(start)
40+
self.assertEqual(self.graph_to_adj(cloned), [[]])
41+
self.assertIsNot(cloned, start)
42+
43+
def test_two_nodes_connected(self):
44+
start = self.build_graph([[2], [1]])
45+
sol = Solution()
46+
cloned = sol.cloneGraph(start)
47+
self.assertEqual(self.graph_to_adj(cloned), [[2], [1]])
48+
self.assertIsNot(cloned, start)
49+
50+
def test_four_cycle(self):
51+
start = self.build_graph([[2,4],[1,3],[2,4],[1,3]])
52+
sol = Solution()
53+
cloned = sol.cloneGraph(start)
54+
self.assertEqual(
55+
self.graph_to_adj(cloned),
56+
[[2,4],[1,3],[2,4],[1,3]]
57+
)
58+
self.assertIsNot(cloned, start)
59+
60+
def test_branch_graph(self):
61+
start = self.build_graph([[2,3], [4], [4], []])
62+
sol = Solution()
63+
cloned = sol.cloneGraph(start)
64+
self.assertEqual(
65+
self.graph_to_adj(cloned),
66+
[[2,3], [4], [4], []]
67+
)
68+
self.assertIsNot(cloned, start)
69+
70+
class TestCloneGraph1(unittest.TestCase):
71+
def build_diamond_graph(self):
72+
# 1-2-3
73+
# \ | /
74+
# 4
75+
76+
n1 = Node(1)
77+
n2 = Node(2)
78+
n3 = Node(3)
79+
n4 = Node(4)
80+
81+
n1.neighbors = [n2, n3]
82+
n2.neighbors = [n1, n4, n3]
83+
n3.neighbors = [n1, n2, n4]
84+
n4.neighbors = [n2, n3]
85+
86+
return n1
87+
88+
def collect_edges(self, node):
89+
# helper to serialize the graph for comparison
90+
visited = set()
91+
q = deque([node])
92+
edges = {}
93+
94+
while q:
95+
cur = q.popleft()
96+
if cur.val in visited:
97+
continue
98+
visited.add(cur.val)
99+
edges[cur.val] = sorted([n.val for n in cur.neighbors])
100+
for n in cur.neighbors:
101+
q.append(n)
102+
103+
return edges
104+
105+
def test_diamond_graph(self):
106+
sol = Solution()
107+
original = self.build_diamond_graph()
108+
cloned = sol.cloneGraph(original)
109+
110+
original_edges = self.collect_edges(original)
111+
cloned_edges = self.collect_edges(cloned)
112+
113+
self.assertEqual(original_edges, cloned_edges)
114+
115+
116+
if __name__ == "__main__":
117+
unittest.main()

0 commit comments

Comments
 (0)