Facebook Pixel

1490. Clone N-ary Tree

Problem Description

This problem asks you to create a deep copy (clone) of an N-ary tree. An N-ary tree is a tree where each node can have any number of children (not just 2 like in a binary tree).

Each node in the tree has two properties:

  • val: an integer value stored in the node
  • children: a list of child nodes (which can be empty if the node is a leaf)

A deep copy means creating an entirely new tree with the same structure and values as the original, but where all nodes are new objects in memory. Changes to the cloned tree should not affect the original tree and vice versa.

The solution uses recursion to traverse the tree:

  1. If the current node is None, return None (base case)
  2. For each child of the current node, recursively clone that child
  3. Create a new node with the same value as the current node and the list of cloned children
  4. Return the newly created node

The recursive approach ensures that every node in the tree is visited and cloned, maintaining the exact structure of the original tree. The time complexity is O(n) where n is the number of nodes, as each node is visited once. The space complexity is also O(n) for storing the cloned tree.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: An N-ary tree is a special type of graph where each node can have multiple children and there are no cycles. Trees are acyclic connected graphs.

Is it a tree?

  • Yes: The problem explicitly states we're working with an N-ary tree structure where each node has a value and a list of children.

DFS

  • Conclusion: Since we're dealing with a tree structure, the flowchart directs us to use DFS (Depth-First Search).

The flowchart suggests using DFS for this tree problem, which makes perfect sense because:

  1. We need to traverse every node in the tree to create a complete clone
  2. DFS naturally follows the recursive structure of trees
  3. For each node, we need to process all its children before moving up, which aligns with DFS's approach of going deep before going wide
  4. The recursive nature of DFS allows us to easily clone each subtree and attach it to the parent node

In this problem, DFS is implemented through recursion where:

  • We visit a node
  • Clone all its children recursively (going deep into each child subtree)
  • Create a new node with the cloned children
  • Return the newly created node

This approach ensures we create a complete deep copy of the entire tree structure.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to create a deep copy of a tree, we need to think about what makes a tree unique: its recursive structure. Each node in a tree can be viewed as the root of its own subtree. This observation is key to solving the problem.

The natural way to think about cloning a tree is to break it down into smaller problems:

  • To clone a tree, we need to clone its root and all its subtrees
  • To clone each subtree, we apply the same logic recursively

This leads us to a recursive DFS approach. Starting from the root, we can think of the cloning process as:

  1. If we encounter an empty node (None), there's nothing to clone - return None
  2. For any non-empty node, we need to create a new node with the same value
  3. But this new node also needs to have cloned versions of all its children

The beauty of recursion here is that we don't need to worry about how deep the tree goes or how many children each node has. We simply trust that our recursive function will correctly clone each child subtree, and we collect all these cloned children to attach to our new node.

The key insight is that we must clone the children first before creating the parent node. This is because when we create a new node, we need to pass it the list of already-cloned children. This naturally leads to a post-order traversal pattern where we process children before their parent.

By using list comprehension [self.cloneTree(child) for child in root.children], we elegantly handle the case where a node can have any number of children (the N-ary nature of the tree), making our solution both concise and general.

Learn more about Tree, Depth-First Search and Breadth-First Search patterns.

Solution Approach

The solution implements a recursive DFS approach to clone the N-ary tree. Let's walk through the implementation step by step:

Base Case: The function first checks if root is None. This handles two scenarios:

  • When the original tree is empty
  • When we reach a leaf node's non-existent child during recursion

In either case, we return None since there's nothing to clone.

Recursive Case: For any non-null node, the cloning process involves two steps:

  1. Clone all children recursively:

    children = [self.cloneTree(child) for child in root.children]

    This line uses list comprehension to iterate through each child of the current node and recursively clone it. The recursive call self.cloneTree(child) will:

    • Go deep into each child's subtree
    • Clone all descendants before returning
    • Return a reference to the cloned child node
  2. Create and return the cloned node:

    return Node(root.val, children)

    After all children have been cloned, we create a new Node object with:

    • The same value as the original node (root.val)
    • The list of cloned children we just created

Data Structure: The solution uses the provided Node class which contains:

  • val: stores the integer value
  • children: a list that holds references to child nodes

Pattern - Post-order Traversal: The algorithm follows a post-order traversal pattern where children are processed before their parent. This ensures that when we create a parent node, all its children have already been cloned and are ready to be attached.

Time and Space Complexity:

  • Time: O(n) where n is the total number of nodes, as we visit each node exactly once
  • Space: O(n) for the cloned tree plus O(h) for the recursion stack, where h is the height of the tree

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through cloning a small N-ary tree to illustrate the solution approach.

Original Tree:

      1
    / | \
   3  2  4
  / \
 5   6

Step-by-step execution:

  1. Start with root (Node 1):

    • cloneTree(Node1) is called
    • Node1 is not None, so we proceed to clone its children: [Node3, Node2, Node4]
  2. Clone first child (Node 3):

    • cloneTree(Node3) is called recursively
    • Node3 has children: [Node5, Node6]
    • Must clone Node3's children first
  3. Clone Node 3's children:

    • cloneTree(Node5) is called
      • Node5 has no children, so children = []
      • Creates and returns new Node(5, [])
    • cloneTree(Node6) is called
      • Node6 has no children, so children = []
      • Creates and returns new Node(6, [])
    • Now we can create new Node(3, [clone of Node5, clone of Node6])
  4. Clone second child (Node 2):

    • cloneTree(Node2) is called
    • Node2 has no children, so children = []
    • Creates and returns new Node(2, [])
  5. Clone third child (Node 4):

    • cloneTree(Node4) is called
    • Node4 has no children, so children = []
    • Creates and returns new Node(4, [])
  6. Complete root cloning:

    • All children of Node1 have been cloned
    • Creates and returns new Node(1, [clone of Node3, clone of Node2, clone of Node4])

Result: A completely new tree with the same structure and values, where:

  • Every node is a new object in memory
  • The structure exactly matches the original
  • Changes to either tree won't affect the other

The recursion stack at its deepest point (when processing Node5 or Node6) looks like:

cloneTree(Node1) → cloneTree(Node3) → cloneTree(Node5)

This demonstrates the post-order nature of the traversal - we build the tree from the leaves up, ensuring children are cloned before their parents.

Solution Implementation

1"""
2# Definition for a Node.
3class Node:
4    def __init__(self, val=None, children=None):
5        self.val = val
6        self.children = children if children is not None else []
7"""
8
9
10class Solution:
11    def cloneTree(self, root: 'Node') -> 'Node':
12        """
13        Creates a deep copy of an N-ary tree.
14
15        Args:
16            root: The root node of the N-ary tree to be cloned.
17
18        Returns:
19            The root node of the cloned N-ary tree.
20        """
21        # Base case: if the root is None, return None
22        if root is None:
23            return None
24
25        # Recursively clone all children nodes
26        cloned_children = [self.cloneTree(child) for child in root.children]
27
28        # Create a new node with the same value and the cloned children
29        return Node(root.val, cloned_children)
30
1/*
2// Definition for a Node.
3class Node {
4    public int val;
5    public List<Node> children;
6
7    public Node() {
8        children = new ArrayList<Node>();
9    }
10
11    public Node(int _val) {
12        val = _val;
13        children = new ArrayList<Node>();
14    }
15
16    public Node(int _val, ArrayList<Node> _children) {
17        val = _val;
18        children = _children;
19    }
20};
21*/
22
23class Solution {
24    /**
25     * Creates a deep copy of an N-ary tree.
26     *
27     * @param root The root node of the original tree to be cloned
28     * @return The root node of the cloned tree, or null if the input is null
29     */
30    public Node cloneTree(Node root) {
31        // Base case: if the node is null, return null
32        if (root == null) {
33            return null;
34        }
35
36        // Create a list to store the cloned children nodes
37        ArrayList<Node> clonedChildren = new ArrayList<>();
38
39        // Recursively clone each child node
40        for (Node child : root.children) {
41            clonedChildren.add(cloneTree(child));
42        }
43
44        // Create and return a new node with the same value and cloned children
45        return new Node(root.val, clonedChildren);
46    }
47}
48
1/*
2// Definition for a Node.
3class Node {
4public:
5    int val;
6    vector<Node*> children;
7
8    Node() {}
9
10    Node(int _val) {
11        val = _val;
12    }
13
14    Node(int _val, vector<Node*> _children) {
15        val = _val;
16        children = _children;
17    }
18};
19*/
20
21class Solution {
22public:
23    /**
24     * Creates a deep copy of an N-ary tree
25     * @param root - pointer to the root node of the original tree
26     * @return pointer to the root node of the cloned tree
27     */
28    Node* cloneTree(Node* root) {
29        // Base case: if the node is null, return null
30        if (!root) {
31            return nullptr;
32        }
33
34        // Create a vector to store cloned children nodes
35        vector<Node*> clonedChildren;
36
37        // Recursively clone each child node
38        for (Node* child : root->children) {
39            clonedChildren.push_back(cloneTree(child));
40        }
41
42        // Create and return a new node with the same value and cloned children
43        return new Node(root->val, clonedChildren);
44    }
45};
46
1/**
2 * Definition for a Node.
3 */
4class Node {
5    val: number;
6    children: Node[];
7
8    constructor(val?: number, children?: Node[]) {
9        this.val = val === undefined ? 0 : val;
10        this.children = children === undefined ? [] : children;
11    }
12}
13
14/**
15 * Creates a deep copy of an N-ary tree
16 * @param root - The root node of the original tree
17 * @returns The root node of the cloned tree
18 */
19function cloneTree(root: Node | null): Node | null {
20    // Base case: if the node is null, return null
21    if (!root) {
22        return null;
23    }
24
25    // Create an array to store cloned children nodes
26    const clonedChildren: Node[] = [];
27
28    // Recursively clone each child node
29    for (const child of root.children) {
30        const clonedChild = cloneTree(child);
31        if (clonedChild) {
32            clonedChildren.push(clonedChild);
33        }
34    }
35
36    // Create and return a new node with the same value and cloned children
37    return new Node(root.val, clonedChildren);
38}
39

Time and Space Complexity

Time Complexity: O(n) where n is the total number of nodes in the N-ary tree. The algorithm visits each node exactly once during the traversal. For each node, it performs constant time operations (creating a new node and assigning values), and then recursively processes all its children. Since every node is visited once, the total time complexity is linear with respect to the number of nodes.

Space Complexity: O(n) for the space needed to create the cloned tree, which contains n nodes identical in structure to the original tree. Additionally, the recursive call stack contributes O(h) space where h is the height of the tree. In the worst case of a skewed tree (essentially a linked list), h could be O(n), while in a balanced tree, h would be O(log n). Therefore, the overall space complexity is O(n) considering both the new tree structure and the recursion stack.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Shallow Copy Instead of Deep Copy

A common mistake is accidentally creating a shallow copy by reusing references to the original children nodes instead of creating new ones.

Incorrect Implementation:

def cloneTree(self, root: 'Node') -> 'Node':
    if root is None:
        return None
    # WRONG: This reuses the same children list reference
    return Node(root.val, root.children)

Why it's wrong: This creates a new node but points to the same children objects as the original tree. Any modifications to the children in either tree would affect both trees.

Correct Approach: Always recursively clone the children to create new node objects:

cloned_children = [self.cloneTree(child) for child in root.children]
return Node(root.val, cloned_children)

2. Not Handling Empty Children List Properly

Some implementations might incorrectly assume that leaf nodes have None as children instead of an empty list.

Incorrect Assumption:

def cloneTree(self, root: 'Node') -> 'Node':
    if root is None:
        return None
    # WRONG: Assumes children could be None
    if root.children is None:
        return Node(root.val, None)
    # ... rest of the code

Why it's wrong: According to the Node definition, children is always a list (possibly empty), never None. The constructor ensures this with children if children is not None else [].

Correct Approach: Trust that root.children is always a list and iterate over it directly. An empty list will naturally result in no iterations and an empty children list for the cloned node.

3. Modifying the Original Tree During Cloning

Accidentally modifying the original tree's structure or values during the cloning process.

Incorrect Implementation:

def cloneTree(self, root: 'Node') -> 'Node':
    if root is None:
        return None
    # WRONG: Modifying original node's children list
    root.children = [self.cloneTree(child) for child in root.children]
    return Node(root.val, root.children)

Why it's wrong: This replaces the original node's children with cloned children, destroying the original tree structure.

Correct Approach: Always create new variables for cloned data without modifying the original:

cloned_children = [self.cloneTree(child) for child in root.children]
return Node(root.val, cloned_children)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More