Start learning free

LESSON 480 OF 539 · GRAPHS

Clone Graph in C#: Breadth-First Search with a Dictionary

Practice O(V + E) Read the full explanation

You are given node 1 of a connected, undirected graph. Return a deep copy (a clone) of the graph.

Each node holds a value and the list of its neighbors; the node class is shown in the starter code. The graph has n nodes, numbered from 1 to n, and node i has the value i. The graph is connected, so every node can be reached from node 1 by going from neighbor to neighbor.

A deep copy is made of exactly n brand-new nodes, one for each node of the graph, with the same value. The neighbor list of each new node holds the new nodes of the original node's neighbors, in the same order. No node of the copy may be a node of the original graph. Return the new node 1. If the graph is empty (n = 0), you get null and return null.

The examples show a graph by its adjacency lists: adjList[i - 1] holds the values of node i's neighbors. After your function returns, the tests walk your copy from the node you returned, print the neighbors of each node, node by node, and report any node that belongs to the original graph.

 

Example 1:

Input: adjList = [[2, 4], [1, 3], [2, 4], [1, 3]]
Output: [[2, 4], [1, 3], [2, 4], [1, 3]]
Explanation: The graph has 4 nodes, joined in a square:
    1 - 2
    |   |
    4 - 3
Node 1's neighbors are nodes 2 and 4, node 2's are nodes 1 and 3, node 3's are nodes 2 and 4,
and node 4's are nodes 1 and 3. The copy has the same shape, made of 4 new nodes.

Example 2:

Input: adjList = [[]]
Output: [[]]
Explanation: The graph is a single node, node 1, with no neighbors. The copy is one new node with the value 1.

Example 3:

Input: adjList = []
Output: []
Explanation: The graph is empty: the function gets null and returns null.

 

Constraints:

  • 0 <= n <= 5 * 104
  • Node i has the value i, for 1 <= i <= n.
  • There are at most 105 edges. No edge joins a node to itself, and no edge appears twice.
  • The graph is connected.

Note:

Your algorithm should run in O(V + E) time, where V is the number of nodes and E the number of edges, and use O(V) extra space.

The tutorial walks the graph with a BFS, as in Course Schedule, and keeps a hash map from each original node to its copy. A node's copy is made the first time the search meets the node, and that is also when the node joins the queue. When a node leaves the queue, the copies of its neighbors are appended to its copy's list. The map is what stops the search from going around a cycle forever: a node that already has a copy is never made or queued again. A recursive DFS with the same map works too.

STEP 1 OF 13
    Ctrl + Enter to check

    C# · GRAPHS

    Clone Graph in C#, explained

    Problem overview

    CloneGraph receives node, node 1 of a connected, undirected graph, and returns a deep copy of the whole graph: the copy of node 1. The lesson provides the Node class: it has a public int val, a public List<Node> neighbors, and a constructor Node(int x) that sets the value and starts an empty neighbor list.

    A deep copy has one brand-new node for every original node, with the same value. Each new node's neighbor list holds the new nodes of the original's neighbors, in the same order. No node of the copy may be a node of the original. For an empty graph, node is null and the method returns null.

    In the first example the four nodes form a square: node 1 is next to 2 and 4, node 2 to 1 and 3, node 3 to 2 and 4, and node 4 to 1 and 3. The copy has the same shape, built from four new nodes.

    Approach: a map from original to copy

    Copying a node is easy; the difficulty is the links. A node's neighbors link back to it, so the graph is full of cycles, and a naive recursive copy would chase them forever. It would also need to know whether a neighbor has already been copied, so that two nodes pointing at the same neighbor point at the same new node and not at two different ones.

    Both problems are solved by a Dictionary<Node, Node> from each original node to its copy. A node's copy is made the first time the search meets it. That is also the moment the node joins the queue of a breadth-first search. A node that already has a copy is never copied or queued again, which stops the search from circling a cycle.

    When a node leaves the queue, the copies of its neighbors are appended, in order, to its own copy's list.

    Step by step

    1. Handle the empty graph. If node is null, return null. The next lines read node.val.
    2. Create copies, a Dictionary<Node, Node>. Node does not define its own Equals or GetHashCode, so the dictionary compares nodes by identity: two different nodes are different keys, even with the same value.
    3. Copy node 1: copies[node] = new Node(node.val). The map both finds the copy of any node met so far and tells which nodes have been met.
    4. Create a Queue<Node> and Enqueue(node). Every node in the queue has a copy already; what the copy lacks is its neighbor list.
    5. Loop while queue.Count > 0, taking the front node with Dequeue into current.
    6. Loop over current.neighbors with foreach. Every edge is seen twice, once from each end, so the total work is proportional to nodes plus edges.
    7. If !copies.ContainsKey(neighbor), this is the first meeting: make its copy and Enqueue it. A node enters the queue only when its copy is made, so each node is queued once.
    8. Link the copies. Add copies[neighbor] to copies[current].neighbors. This line is the heart of the solution: the copy of current points to the copy of neighbor, never to neighbor itself.
    9. Return copies[node], the copy of node 1, after the loop.

    The solution in C#

    class Solution {
        public Node CloneGraph(Node node) {
            if (node == null) {
                return null;
            }
            Dictionary<Node, Node> copies = new Dictionary<Node, Node>();
            copies[node] = new Node(node.val);
            Queue<Node> queue = new Queue<Node>();
            queue.Enqueue(node);
            while (queue.Count > 0) {
                Node current = queue.Dequeue();
                foreach (Node neighbor in current.neighbors) {
                    if (!copies.ContainsKey(neighbor)) {
                        copies[neighbor] = new Node(neighbor.val);
                        queue.Enqueue(neighbor);
                    }
                    copies[current].neighbors.Add(copies[neighbor]);
                }
            }
            return copies[node];
        }
    }
    

    The C# details worth knowing:

    • Reading copies[x] for a missing key throws KeyNotFoundException. The code is safe because a neighbor's copy is always made, in the if, before copies[neighbor] is read on the next line.
    • Dictionary uses reference equality for a class that does not override Equals, which is what this problem needs. A Dictionary<int, Node> keyed by val would also work here because values are unique, but identity does not depend on that.
    • Queue<T> has Enqueue at the back, Dequeue from the front, and Count. Note Count, not Length.
    • Node, Dictionary, Queue and List come from the lesson's setup, so you do not add using lines in the editor. In a file of your own you would need using System.Collections.Generic;.

    Complexity

    Time: O(V + E), where V is the number of nodes and E the number of edges. Each node is queued once, and each node's neighbor list is read once, which covers every edge twice.

    Space: O(V) for the dictionary and the queue, besides the copy itself.

    A recursive depth-first copy with the same dictionary also works, but a long path of tens of thousands of nodes would make the recursion deep. The queue has no such limit.

    Edge cases and common mistakes

    • Empty graph. null in, null out.
    • A single node. A node with value 1 and no neighbors gives a new node with value 1 and an empty list. It is not the original object.
    • Cycles. In the square graph, node 1 is a neighbor of node 2 and node 2 is a neighbor of node 1. The dictionary check stops the search from copying either twice.
    • Adding the original neighbor to the copy's list. The copy would then share nodes with the original graph, which the tests reject.
    • Making the copy inside the queue loop instead of at the first meeting. A node can be reached from several neighbors before it leaves the queue, and would get several copies.
    • Neighbor order. The copy's list follows the original's order, because neighbors are added in the order the original lists them.

    Testing

    Walking the copy of the four-node square from the returned node, and printing each node's neighbors and whether the node is one of the originals, gives:

    1: 2,4  sameObjectAsOriginal=False
    2: 1,3  sameObjectAsOriginal=False
    4: 1,3  sameObjectAsOriginal=False
    3: 2,4  sameObjectAsOriginal=False

    The nodes appear in the order the walk reaches them: 1, 2, 4, 3. Passing null returns null. A single node with value 1 and no neighbors gives a node with value 1, an empty neighbor list, and a different object from the original.

    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free