1506. Find Root of N-Ary Tree 🔒
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:
-
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.
-
XOR Property: XOR has the property that
a ^ a = 0
anda ^ 0 = a
. If we XOR a value with itself, it cancels out. -
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
- Initialize
-
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
-
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:
- Track which nodes appear as children using a set
- The root would be the only node not in the children set
- 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.
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 EvaluatorExample 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)
wherec
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
andchild
-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;
}
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!