Facebook Pixel

1506. Find Root of N-Ary Tree 🔒

MediumBit ManipulationTreeDepth-First SearchHash Table
Leetcode Link

Problem Description

You are given an array containing all the nodes of an N-ary tree (a tree where each node can have any number of children). Each node in the tree has a unique value. The nodes are given in an arbitrary order - not necessarily in any traversal order.

Your task is to identify and return which node is the root of the tree.

Key Points:

  • An N-ary tree is a tree where each node can have zero or more children
  • All nodes of the tree are provided in the input array
  • Each node has a unique value
  • The nodes in the array are in random order
  • You need to determine which node is the root (the node that has no parent)

Solution Explanation:

The solution uses a clever XOR-based approach to find the root node:

  1. Core Insight: The root node appears only once in the relationships, while all other nodes appear twice - once as a node in the array and once as someone's child.

  2. XOR Property: XOR has the property that a ^ a = 0 and a ^ 0 = a. If we XOR a value with itself, it cancels out.

  3. Algorithm:

    • Initialize x = 0
    • For each node in the array, XOR its value with x
    • For each child of each node, XOR the child's value with x
    • After processing all nodes, x will contain the value of the root node
  4. Why it works:

    • Every non-root node appears twice in the XOR calculation: once when we iterate through the array nodes, and once when we encounter it as a child
    • The root node appears only once (when iterating through array nodes, but never as a child)
    • Due to XOR properties, all non-root values cancel out, leaving only the root's value
  5. Final Step: Find the node in the array whose value equals x and return it.

Time Complexity: O(n) where n is the total number of nodes, as we iterate through all nodes and their children once.

Space Complexity: O(1) as we only use a single variable to store the XOR result.

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. Trees are acyclic connected graphs.

Is it a tree?

  • Yes: The problem explicitly states we're working with an N-ary tree structure where nodes have parent-child relationships.

DFS

  • Yes: This leads us to DFS (Depth-First Search) as the recommended approach for tree problems.

Conclusion: The flowchart suggests using DFS for this tree problem.

However, it's worth noting that the actual solution provided uses a clever XOR-based approach rather than traditional DFS. This demonstrates that while the flowchart guides us to DFS as a valid approach, sometimes mathematical properties can provide more elegant solutions.

Alternative DFS Approach: If we were to solve this using DFS as suggested by the flowchart, we could:

  1. Track which nodes appear as children using a set
  2. The root would be the only node not in the children set
  3. Or use DFS to traverse from each node and identify which one can reach all others

The XOR solution is actually more efficient (O(n) time, O(1) space) compared to a traditional DFS approach that might require O(n) space for tracking visited nodes or children sets.

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

Intuition

The key insight comes from observing the structure of parent-child relationships in a tree. Every node except the root has exactly one parent. This means:

  • The root node appears only in the list of nodes
  • Every other node appears twice in the relationships: once in the node list and once as someone's child

Think of it like a attendance system where we mark present for each appearance:

  • When we go through the node array, we mark each node as "present"
  • When we go through each node's children, we mark those children as "present" again
  • The root never gets marked as a child, so it only appears once

This "appearing twice vs appearing once" pattern is perfect for XOR operation because:

  • a ^ a = 0 (any number XORed with itself becomes 0)
  • a ^ 0 = a (any number XORed with 0 remains unchanged)
  • XOR is commutative and associative, so order doesn't matter

Imagine we have nodes with values [1, 2, 3, 4] where node 1 is root with children 2 and 3, and node 3 has child 4:

  • We XOR all node values: 1 ^ 2 ^ 3 ^ 4
  • We XOR all children values: 2 ^ 3 ^ 4
  • Combined: 1 ^ 2 ^ 3 ^ 4 ^ 2 ^ 3 ^ 4
  • Rearranging: 1 ^ (2 ^ 2) ^ (3 ^ 3) ^ (4 ^ 4)
  • Simplifying: 1 ^ 0 ^ 0 ^ 0 = 1

The root's value remains! This elegant mathematical property allows us to find the root in just one pass through the data, without needing to build the actual tree structure or track parent-child relationships explicitly.

Learn more about Tree and Depth-First Search patterns.

Solution Approach

The implementation leverages the XOR operation to identify the unique root node. Here's the step-by-step breakdown:

1. Initialize XOR accumulator:

x = 0

We start with 0 because 0 ^ a = a for any value a.

2. First pass - XOR all node values:

for node in [tree](/problems/tree_intro):
    x ^= node.val

This XORs every node's value in the array with our accumulator. At this point, x contains the XOR of all node values.

3. Second pass - XOR all children values:

for node in [tree](/problems/tree_intro):
    for child in node.children:
        x ^= child.val

For each node, we iterate through its children list and XOR each child's value. This effectively XORs every non-root node's value a second time.

4. Mathematical result: After both passes:

  • Every non-root node has been XORed exactly twice (once as a node, once as a child)
  • The root node has been XORed exactly once (only as a node, never as a child)
  • Due to the property a ^ a = 0, all non-root values cancel out
  • The value x now equals the root node's value

5. Find and return the root node:

return next(node for node in [tree](/problems/tree_intro) if node.val == x)

We use Python's next() function with a generator expression to find the first (and only) node whose value matches x. This is guaranteed to be the root node.

Time Complexity: O(n * m) where n is the number of nodes and m is the average number of children per node. In the worst case where one node has all others as children, this becomes O(n²), but typically it's O(n) for balanced trees.

Space Complexity: O(1) - we only use a single integer variable x to store the XOR result, regardless of the tree size.

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 a small example to illustrate the XOR solution approach.

Example Tree: Consider an N-ary tree with 4 nodes:

  • Node 5 (root) has children [3, 6]
  • Node 3 has child [2]
  • Node 6 has no children
  • Node 2 has no children

The input array contains: [Node(5), Node(3), Node(6), Node(2)] in random order.

Step 1: Initialize XOR accumulator

x = 0

Step 2: XOR all node values Go through each node in the array:

x = 0 ^ 5 = 5
x = 5 ^ 3 = 6
x = 6 ^ 6 = 0
x = 0 ^ 2 = 2

After this step: x = 2

Step 3: XOR all children values Now iterate through each node's children:

  • Node 5's children: [3, 6]
    x = 2 ^ 3 = 1
    x = 1 ^ 6 = 7
  • Node 3's children: [2]
    x = 7 ^ 2 = 5
  • Node 6's children: [] (none)
  • Node 2's children: [] (none)

After this step: x = 5

Step 4: Understanding the result Let's trace what happened:

  • Node 5: XORed once (in node array)
  • Node 3: XORed twice (in node array + as child of 5)
  • Node 6: XORed twice (in node array + as child of 5)
  • Node 2: XORed twice (in node array + as child of 3)

The overall XOR calculation was:

5 ^ 3 ^ 6 ^ 2 ^ 3 ^ 6 ^ 2
= 5 ^ (3 ^ 3) ^ (6 ^ 6) ^ (2 ^ 2)
= 5 ^ 0 ^ 0 ^ 0
= 5

Step 5: Find and return the root Search the array for the node with value 5:

return Node(5)  # This is our root!

The algorithm correctly identified Node 5 as the root because it was the only node that appeared an odd number of times in our XOR calculation.

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
9from typing import List, Optional
10
11
12class Solution:
13    def findRoot(self, tree: List['Node']) -> 'Node':
14        """
15        Find the root node of an N-ary tree given all nodes in the tree.
16      
17        The root node is the only node that appears once when counting all nodes
18        and their children. All other nodes appear twice (once as a node in the list,
19        once as someone's child).
20      
21        Using XOR property: a ^ a = 0 and a ^ 0 = a
22        All non-root nodes will be XORed twice (canceling out), leaving only the root.
23      
24        Args:
25            tree: List of all nodes in the N-ary tree
26          
27        Returns:
28            The root node of the tree
29        """
30        # Initialize XOR accumulator
31        xor_result = 0
32      
33        # XOR all node values and all children values
34        for node in tree:
35            # XOR current node's value
36            xor_result ^= node.val
37          
38            # XOR all children's values of current node
39            for child in node.children:
40                xor_result ^= child.val
41      
42        # The remaining value after XOR is the root node's value
43        # Find and return the node with this value
44        return next(node for node in tree if node.val == xor_result)
45
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     * Finds the root node of an N-ary tree from a list of all tree nodes.
26     * 
27     * Algorithm: Uses XOR operation to identify the root node.
28     * - Each non-root node appears twice: once in the tree list and once as a child
29     * - The root node appears only once (in the tree list but not as anyone's child)
30     * - XORing all node values and all children values leaves only the root value
31     * 
32     * @param tree List containing all nodes of the N-ary tree
33     * @return The root node of the tree
34     */
35    public Node findRoot(List<Node> tree) {
36        // Initialize XOR accumulator
37        int xorSum = 0;
38      
39        // XOR all node values and their children's values
40        for (Node node : tree) {
41            // XOR current node's value
42            xorSum ^= node.val;
43          
44            // XOR all children's values of current node
45            for (Node child : node.children) {
46                xorSum ^= child.val;
47            }
48        }
49      
50        // Find and return the node whose value equals the XOR result
51        for (int i = 0; ; ++i) {
52            if (tree.get(i).val == xorSum) {
53                return tree.get(i);
54            }
55        }
56    }
57}
58
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    Node* findRoot(vector<Node*> tree) {
24        // Use XOR to find the root value
25        // Each non-root node appears twice: once in the tree array and once as a child
26        // The root appears only once: in the tree array but never as a child
27        // XORing all occurrences will cancel out duplicates, leaving only the root value
28        int xor_sum = 0;
29      
30        // XOR all node values and their children's values
31        for (Node* node : tree) {
32            // XOR current node's value
33            xor_sum ^= node->val;
34          
35            // XOR all children's values of current node
36            for (Node* child : node->children) {
37                xor_sum ^= child->val;
38            }
39        }
40      
41        // Find and return the node with value equal to xor_sum
42        for (int i = 0; ; ++i) {
43            if (tree[i]->val == xor_sum) {
44                return tree[i];
45            }
46        }
47    }
48};
49
1/**
2 * Definition for Node.
3 * class Node {
4 *     val: number
5 *     children: Node[]
6 *     constructor(val?: number, children?: Node[]) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.children = (children===undefined ? [] : children)
9 *     }
10 * }
11 */
12
13/**
14 * Finds the root node of an N-ary tree from an array of all tree nodes.
15 * 
16 * Algorithm explanation:
17 * - The root appears exactly once in the tree array
18 * - All non-root nodes appear twice: once in the tree array and once as a child
19 * - Using XOR operation: a ^ a = 0, so all non-root nodes cancel out
20 * - Only the root node's value remains after XOR operations
21 * 
22 * @param tree - Array containing all nodes of the N-ary tree
23 * @returns The root node of the tree, or null if not found
24 */
25function findRoot(tree: Node[]): Node | null {
26    // XOR accumulator to find the unique root value
27    let xorResult: number = 0;
28  
29    // Iterate through all nodes in the tree
30    for (const currentNode of tree) {
31        // XOR with current node's value (appears once for all nodes)
32        xorResult ^= currentNode.val;
33      
34        // XOR with all children's values (appears once more for non-root nodes)
35        for (const childNode of currentNode.children) {
36            xorResult ^= childNode.val;
37        }
38    }
39  
40    // Find and return the node with the calculated root value
41    return tree.find((node: Node) => node.val === xorResult) || null;
42}
43

Time and Space Complexity

Time Complexity: O(n) where n is the total number of nodes in the tree.

The algorithm iterates through the tree list once, which contains all nodes. For each node, it:

  • XORs the node's value with x - O(1)
  • Iterates through all children of that node and XORs each child's value - O(c) where c is the number of children

Since we visit each node exactly once as a parent and exactly once as a child (except the root which is never a child), the total number of XOR operations is approximately 2n - 1. The final next() operation iterates through the tree list once more in the worst case to find the matching node, adding another O(n). Therefore, the overall time complexity is O(n) + O(n) = O(n).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • The variable x to store the XOR result - O(1)
  • The loop variables node and child - O(1)

The algorithm doesn't create any additional data structures that grow with the input size, making the space complexity constant.

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

Common Pitfalls

1. Assuming Node Values are Hashable Objects Instead of Integers

One common pitfall is assuming that the XOR approach will work with any type of node value. The XOR operation (^) in Python only works with integers, not with arbitrary objects or even strings.

Problem Example:

# This will fail if node.val is a string or object
x ^= node.val  # TypeError if val is not an integer

Solution: If node values aren't integers, use a set-based approach instead:

def findRoot(self, tree: List['Node']) -> 'Node':
    # Create a set of all node values
    all_nodes = {node.val for node in tree}
  
    # Remove all children values (they have parents)
    for node in tree:
        for child in node.children:
            all_nodes.discard(child.val)
  
    # The remaining value is the root
    root_val = all_nodes.pop()
    return next(node for node in tree if node.val == root_val)

2. Not Handling Empty Tree or None Values

The code assumes the tree list is non-empty and all nodes have valid values. If the tree is empty or contains None values, the code will fail.

Problem Example:

# Empty tree case
tree = []
next(node for node in tree if node.val == xor_result)  # StopIteration

# None value case
node.val = None
xor_result ^= node.val  # TypeError: unsupported operand type

Solution: Add validation at the beginning:

def findRoot(self, tree: List['Node']) -> 'Node':
    if not tree:
        return None
  
    xor_result = 0
    for node in tree:
        if node and node.val is not None:
            xor_result ^= node.val
            for child in node.children:
                if child and child.val is not None:
                    xor_result ^= child.val
  
    return next((node for node in tree if node and node.val == xor_result), None)

3. Confusion About Time Complexity

A subtle pitfall is misunderstanding the actual time complexity. While the solution description states O(n), it's actually O(n + e) where n is the number of nodes and e is the total number of edges (parent-child relationships).

Clarification:

  • We iterate through n nodes: O(n)
  • We iterate through all children of all nodes, which equals the total number of edges: O(e)
  • Total: O(n + e)
  • In a tree with n nodes, there are exactly n-1 edges, so this simplifies to O(n)

4. Integer Overflow with Large Values

In languages with fixed integer sizes (like Java or C++), XORing large values might cause unexpected behavior due to integer overflow. Python handles arbitrary precision integers, but this could be an issue when porting the solution.

Solution for Other Languages: Use a HashSet-based approach or ensure values fit within the integer range:

// Java example with overflow protection
public Node findRoot(List<Node> tree) {
    long xorResult = 0L;  // Use long to reduce overflow risk
    for (Node node : tree) {
        xorResult ^= node.val;
        for (Node child : node.children) {
            xorResult ^= child.val;
        }
    }
  
    for (Node node : tree) {
        if (node.val == (int)xorResult) {
            return node;
        }
    }
    return null;
}
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

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

Load More