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 ofchild_i
- If
isLeft_i == 1
, thenchild_i
is the left child ofparent_i
- If
isLeft_i == 0
, thenchild_i
is the right child ofparent_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.
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:
-
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. -
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 ournodes
collection but is NOT in thechildren
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 objectschildren
: A set to track all nodes that appear as children
Step-by-step Implementation:
-
Initialize data structures:
nodes = defaultdict(TreeNode) children = set()
-
Process each description: For each
[parent, child, isLeft]
indescriptions
: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]
-
Find the root node: The root is the node that exists in
nodes
but not inchildren
:root = (set(nodes.keys()) - children).pop()
This set difference operation gives us exactly one element - the root node's value.
-
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 EvaluatorExample 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
Which algorithm should you use to find a node that is close to the root of the tree?
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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
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!