Facebook Pixel

2196. Create Binary Tree From Descriptions

Problem Description

You are given a 2D integer array descriptions where each element descriptions[i] = [parent_i, child_i, isLeft_i] represents a parent-child relationship in a binary tree with unique values.

The relationship is defined as:

  • parent_i is the parent node of child_i
  • If isLeft_i == 1, then child_i is the left child of parent_i
  • If isLeft_i == 0, then child_i is the right child of parent_i

Your task is to construct the binary tree from these descriptions and return the root node of the tree.

The problem guarantees that the descriptions will form a valid binary tree.

Example Understanding:

If you have descriptions like [[20,15,1], [20,17,0], [50,20,1], [50,80,0]], this means:

  • Node 20 has left child 15 and right child 17
  • Node 50 has left child 20 and right child 80
  • Node 50 is the root (since it appears as a parent but never as a child)

The key insight is that the root node will be the only node that appears as a parent but never as a child in any description.

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

Intuition

The main challenge is to build the tree from scattered parent-child relationships and identify which node is the root.

Let's think about what information we have:

  • Each description tells us about one parent-child relationship
  • We need to connect all these relationships to form a complete tree
  • We need to find the root node

The key observation is: every node except the root will appear as a child in at least one description. The root node is special - it will only appear as a parent, never as a child.

This leads us to a two-step approach:

  1. Build the tree structure: As we process each description, we need to create nodes if they don't exist yet and establish the parent-child connections. Using a hash table (nodes) makes it easy to check if a node already exists and to access any node by its value.

  2. Find the root: We track all nodes that appear as children in a set (children). After processing all descriptions, the root will be the only node that exists in our nodes collection but is NOT in the children set. This is because the root has no parent, so it never appears as a child.

Why use defaultdict(TreeNode)? This clever trick allows us to automatically create a new TreeNode whenever we access a key that doesn't exist yet, simplifying our code. Though in the actual solution, we still check and create nodes explicitly for clarity.

The beauty of this approach is that we don't need to worry about the order of descriptions - we can process them in any order and still correctly build the tree and identify the root.

Learn more about Tree and Binary Tree patterns.

Solution Approach

We use a hash table-based approach to construct the binary tree from the given descriptions.

Data Structures Used:

  • nodes: A hash table (dictionary) where keys are node values and values are TreeNode objects
  • children: A set to track all nodes that appear as children

Step-by-step Implementation:

  1. Initialize data structures:

    nodes = defaultdict(TreeNode)
    children = set()
  2. Process each description: For each [parent, child, isLeft] in descriptions:

    a. Create or retrieve parent node:

    if parent not in nodes:
        nodes[parent] = TreeNode(parent)

    b. Create or retrieve child node:

    if child not in nodes:
        nodes[child] = TreeNode(child)

    c. Track child nodes:

    children.add(child)

    d. Establish parent-child relationship:

    if isLeft:
        nodes[parent].left = nodes[child]
    else:
        nodes[parent].right = nodes[child]
  3. Find the root node: The root is the node that exists in nodes but not in children:

    root = (set(nodes.keys()) - children).pop()

    This set difference operation gives us exactly one element - the root node's value.

  4. Return the root:

    return nodes[root]

Time Complexity: O(n) where n is the number of descriptions, as we iterate through the descriptions once and perform constant-time operations for each.

Space Complexity: O(n) for storing the nodes in the hash table and the children set.

The elegance of this solution lies in how it leverages the property that only the root node never appears as a child, making it easy to identify after building all the relationships.

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 with descriptions: [[1,2,1], [1,3,0], [2,4,1]]

This represents:

  • Node 1 has left child 2
  • Node 1 has right child 3
  • Node 2 has left child 4

Step 1: Initialize data structures

  • nodes = {} (empty dictionary)
  • children = set() (empty set)

Step 2: Process first description [1,2,1]

  • Parent = 1, Child = 2, isLeft = 1
  • Node 1 doesn't exist, create it: nodes[1] = TreeNode(1)
  • Node 2 doesn't exist, create it: nodes[2] = TreeNode(2)
  • Add 2 to children set: children = {2}
  • Since isLeft = 1, set left child: nodes[1].left = nodes[2]
  • Current state: nodes = {1: TreeNode(1), 2: TreeNode(2)}, children = {2}

Step 3: Process second description [1,3,0]

  • Parent = 1, Child = 3, isLeft = 0
  • Node 1 already exists in nodes
  • Node 3 doesn't exist, create it: nodes[3] = TreeNode(3)
  • Add 3 to children set: children = {2, 3}
  • Since isLeft = 0, set right child: nodes[1].right = nodes[3]
  • Current state: nodes = {1: TreeNode(1), 2: TreeNode(2), 3: TreeNode(3)}, children = {2, 3}

Step 4: Process third description [2,4,1]

  • Parent = 2, Child = 4, isLeft = 1
  • Node 2 already exists in nodes
  • Node 4 doesn't exist, create it: nodes[4] = TreeNode(4)
  • Add 4 to children set: children = {2, 3, 4}
  • Since isLeft = 1, set left child: nodes[2].left = nodes[4]
  • Final state: nodes = {1: TreeNode(1), 2: TreeNode(2), 3: TreeNode(3), 4: TreeNode(4)}, children = {2, 3, 4}

Step 5: Find the root

  • All nodes: {1, 2, 3, 4}
  • Children nodes: {2, 3, 4}
  • Root = nodes - children = {1, 2, 3, 4} - {2, 3, 4} = {1}
  • Root value is 1

Step 6: Return the root node

  • Return nodes[1], which is the TreeNode with value 1

The resulting tree structure:

      1
     / \
    2   3
   /
  4

Node 1 is correctly identified as the root because it's the only node that never appears as a child in any description.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8from typing import List, Optional
9from collections import defaultdict
10
11class Solution:
12    def createBinaryTree(self, descriptions: List[List[int]]) -> Optional[TreeNode]:
13        # Dictionary to store all nodes by their values
14        # Using defaultdict to automatically create TreeNode when accessing new keys
15        nodes = {}
16      
17        # Set to track all nodes that appear as children
18        children = set()
19      
20        # Process each parent-child relationship
21        for parent_val, child_val, is_left in descriptions:
22            # Create parent node if it doesn't exist
23            if parent_val not in nodes:
24                nodes[parent_val] = TreeNode(parent_val)
25          
26            # Create child node if it doesn't exist
27            if child_val not in nodes:
28                nodes[child_val] = TreeNode(child_val)
29          
30            # Track this node as a child (cannot be root)
31            children.add(child_val)
32          
33            # Attach child to parent's left or right based on is_left flag
34            if is_left:
35                nodes[parent_val].left = nodes[child_val]
36            else:
37                nodes[parent_val].right = nodes[child_val]
38      
39        # Find root node: the only node that appears as parent but never as child
40        # Get the difference between all nodes and child nodes
41        root_val = (set(nodes.keys()) - children).pop()
42      
43        # Return the root node
44        return nodes[root_val]
45
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    public TreeNode createBinaryTree(int[][] descriptions) {
18        // Map to store all nodes by their values
19        Map<Integer, TreeNode> nodeMap = new HashMap<>();
20      
21        // Set to track all nodes that are children (have a parent)
22        Set<Integer> childrenSet = new HashSet<>();
23      
24        // Process each description to build the tree relationships
25        for (int[] description : descriptions) {
26            int parentValue = description[0];
27            int childValue = description[1];
28            int isLeft = description[2];
29          
30            // Create parent node if it doesn't exist
31            if (!nodeMap.containsKey(parentValue)) {
32                nodeMap.put(parentValue, new TreeNode(parentValue));
33            }
34          
35            // Create child node if it doesn't exist
36            if (!nodeMap.containsKey(childValue)) {
37                nodeMap.put(childValue, new TreeNode(childValue));
38            }
39          
40            // Establish parent-child relationship based on isLeft flag
41            TreeNode parentNode = nodeMap.get(parentValue);
42            TreeNode childNode = nodeMap.get(childValue);
43          
44            if (isLeft == 1) {
45                parentNode.left = childNode;
46            } else {
47                parentNode.right = childNode;
48            }
49          
50            // Mark this node as a child
51            childrenSet.add(childValue);
52        }
53      
54        // Find and return the root node (node that is not a child of any other node)
55        for (Map.Entry<Integer, TreeNode> entry : nodeMap.entrySet()) {
56            if (!childrenSet.contains(entry.getKey())) {
57                return entry.getValue();
58            }
59        }
60      
61        // Return null if no root found (should not happen with valid input)
62        return null;
63    }
64}
65
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    TreeNode* createBinaryTree(vector<vector<int>>& descriptions) {
15        // Map to store all nodes by their values
16        unordered_map<int, TreeNode*> nodeMap;
17      
18        // Set to track all nodes that are children (have a parent)
19        unordered_set<int> childNodes;
20      
21        // Process each parent-child relationship description
22        for (const auto& description : descriptions) {
23            int parentVal = description[0];
24            int childVal = description[1];
25            bool isLeftChild = description[2];
26          
27            // Create parent node if it doesn't exist
28            if (nodeMap.find(parentVal) == nodeMap.end()) {
29                nodeMap[parentVal] = new TreeNode(parentVal);
30            }
31          
32            // Create child node if it doesn't exist
33            if (nodeMap.find(childVal) == nodeMap.end()) {
34                nodeMap[childVal] = new TreeNode(childVal);
35            }
36          
37            // Connect child to parent based on left/right position
38            if (isLeftChild) {
39                nodeMap[parentVal]->left = nodeMap[childVal];
40            } else {
41                nodeMap[parentVal]->right = nodeMap[childVal];
42            }
43          
44            // Mark this node as a child
45            childNodes.insert(childVal);
46        }
47      
48        // Find and return the root node (node that is not a child of any other node)
49        for (const auto& [nodeVal, nodePtr] : nodeMap) {
50            if (childNodes.find(nodeVal) == childNodes.end()) {
51                return nodePtr;
52            }
53        }
54      
55        // Return nullptr if no root found (shouldn't happen with valid input)
56        return nullptr;
57    }
58};
59
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Creates a binary tree from parent-child relationship descriptions
17 * @param descriptions - Array of [parent, child, isLeft] tuples where:
18 *                      parent: parent node value
19 *                      child: child node value
20 *                      isLeft: 1 if child is left child, 0 if right child
21 * @returns The root node of the constructed binary tree
22 */
23function createBinaryTree(descriptions: number[][]): TreeNode | null {
24    // Map to store all nodes by their values
25    const nodeMap: Map<number, TreeNode> = new Map<number, TreeNode>();
26  
27    // Set to track all nodes that are children (not root)
28    const childNodeValues: Set<number> = new Set<number>();
29  
30    // Process each parent-child relationship
31    for (const [parentValue, childValue, isLeftChild] of descriptions) {
32        // Create parent node if it doesn't exist
33        if (!nodeMap.has(parentValue)) {
34            nodeMap.set(parentValue, new TreeNode(parentValue));
35        }
36      
37        // Create child node if it doesn't exist
38        if (!nodeMap.has(childValue)) {
39            nodeMap.set(childValue, new TreeNode(childValue));
40        }
41      
42        // Establish parent-child relationship
43        const parentNode: TreeNode = nodeMap.get(parentValue)!;
44        const childNode: TreeNode = nodeMap.get(childValue)!;
45      
46        if (isLeftChild === 1) {
47            parentNode.left = childNode;
48        } else {
49            parentNode.right = childNode;
50        }
51      
52        // Mark this value as a child node
53        childNodeValues.add(childValue);
54    }
55  
56    // Find and return the root node (node that is not a child of any other node)
57    for (const [nodeValue, node] of nodeMap) {
58        if (!childNodeValues.has(nodeValue)) {
59            return node;
60        }
61    }
62  
63    // Should not reach here if input is valid
64    return null;
65}
66

Time and Space Complexity

The time complexity is O(n), where n is the length of descriptions. Each description is processed exactly once in the for loop. Within the loop, all operations are constant time: checking if nodes exist in the dictionary, creating TreeNode objects, adding to a set, and assigning left/right children. The final operation of finding the root by computing the set difference set(nodes.keys()) - children takes O(n) time as it involves iterating through all nodes once.

The space complexity is O(n), where n is the length of descriptions. The nodes dictionary stores at most 2n unique nodes (in the worst case, each description introduces two unique nodes, though practically it's less due to shared nodes). The children set stores at most n child nodes (one per description). The TreeNode objects themselves also consume O(n) space. Therefore, the overall space complexity is O(n).

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

Common Pitfalls

1. Assuming Node Values Start from 0 or 1

A common mistake is assuming node values are sequential or start from a specific number. The problem states nodes have unique values, but they can be any integers.

Pitfall Example:

# WRONG: Assuming values are indices
nodes = [None] * len(descriptions)  # This won't work for arbitrary values

Solution: Use a dictionary/hash map to store nodes by their actual values:

nodes = {}  # Correct: Can handle any integer values

2. Creating Duplicate TreeNode Objects

Without checking if a node already exists, you might create multiple TreeNode objects for the same value, breaking the tree structure.

Pitfall Example:

# WRONG: Always creating new nodes
for parent_val, child_val, is_left in descriptions:
    parent = TreeNode(parent_val)  # Creates new node every time!
    child = TreeNode(child_val)    # Same issue here

Solution: Check if the node exists before creating:

if parent_val not in nodes:
    nodes[parent_val] = TreeNode(parent_val)
if child_val not in nodes:
    nodes[child_val] = TreeNode(child_val)

3. Not Handling the Case Where Parent Appears After Child

The descriptions might not be in order - a child node might appear as a parent in earlier descriptions before its own parent relationship is defined.

Pitfall Example:

# WRONG: Assuming parent nodes are always created before being referenced as children
# Given: [[15,10,1], [20,15,0]]  # 15 appears as parent before being defined as child

Solution: The hash map approach naturally handles this by creating nodes on-demand regardless of order.

4. Incorrectly Finding the Root Node

Some might try to find the root by looking for the first parent in descriptions or making assumptions about which node is the root.

Pitfall Example:

# WRONG: Assuming first parent is root
root = descriptions[0][0]  # Not necessarily the root!

# WRONG: Assuming largest/smallest value is root
root = max(nodes.keys())  # Arbitrary assumption

Solution: Use set difference to find the node that never appears as a child:

root_val = (set(nodes.keys()) - children).pop()

5. Memory Inefficiency with defaultdict

While the comment mentions using defaultdict, the actual code doesn't use it. If you do use it incorrectly, it can create unnecessary nodes.

Pitfall Example:

from collections import defaultdict
# WRONG: This creates TreeNode(0) for any accessed key
nodes = defaultdict(TreeNode)  # Creates nodes with val=0!

Solution: Either use a regular dictionary with existence checks (as shown in the solution) or use defaultdict with a lambda:

# Option 1: Regular dict (preferred for clarity)
nodes = {}
if parent_val not in nodes:
    nodes[parent_val] = TreeNode(parent_val)

# Option 2: defaultdict with lambda (more complex)
nodes = defaultdict(lambda: None)
# Still need to check and create properly
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More