Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# 2196. Create Binary Tree From Descriptions

[LeetCode Link](https://leetcode.com/problems/create-binary-tree-from-descriptions/)

Difficulty: Medium
Topics: Array, Hash Table, Tree, Binary Tree
Acceptance Rate: 82.3%

## Hints

### Hint 1

The input gives you edges (parent → child relationships) scattered in arbitrary
order, but you need to assemble an actual linked tree structure. Think about how
you can look up or create a node by its *value* quickly, no matter when you first
encounter it. Which data structure lets you find "the node for value `v`" in
constant time?

### Hint 2

A hash map from value → `*TreeNode` is your friend. As you scan the descriptions,
you will repeatedly need the node objects for both `parent` and `child`. Create
them on first sight and reuse them afterward, so that wiring up `parent.Left` or
`parent.Right` connects to the *same* shared node every time it is referenced.

### Hint 3

The only remaining puzzle is: which node is the root? The root is the single node
that is **never a child** of anyone. So while you process the edges, keep a set of
all values that appear in the `child` position. After the pass, the root is the
one value that exists in your node map but is *not* in the child set. With a hash
map plus a child-set, the whole thing is one linear pass to build, and one scan to
find the root.

## Approach

The problem hands you a flat list of parent/child edges and asks you to rebuild
the binary tree they describe. The challenge is purely bookkeeping: the same value
can appear as a parent in one description and as a child in another, in any order,
so we need a way to refer to "the node for value `v`" consistently.

**Step 1 — Map values to nodes.**
Maintain `nodes`, a `map[int]*TreeNode`. Define a small helper that returns the
node for a value, creating and inserting it on first use. This guarantees there is
exactly one `*TreeNode` per value, and any `Left`/`Right` pointer we set refers to
that shared instance.

**Step 2 — Wire up the edges.**
For each `[parent, child, isLeft]`:
- Fetch (or create) the parent node and the child node via the helper.
- If `isLeft == 1`, set `parent.Left = child`, otherwise `parent.Right = child`.
- Record `child` in a `children` set (`map[int]bool`) so we know it has a parent.

**Step 3 — Find the root.**
A valid binary tree has exactly one root, and the root is the only value that is
never listed as someone's child. Scan the keys of `nodes`; the value that is *not*
present in `children` is the root. Return its node.

*Walkthrough on Example 1:*
`[[20,15,1],[20,17,0],[50,20,1],[50,80,0],[80,19,1]]`. Processing the edges
creates nodes for 20, 15, 17, 50, 80, 19 and links them: 20→(15 left, 17 right),
50→(20 left, 80 right), 80→(19 left). The `children` set is {15, 17, 20, 80, 19}.
The only value in `nodes` but not in `children` is **50**, so 50 is the root —
matching the expected output.

This is the optimal approach: every description is touched a constant number of
times, and a hash map gives O(1) lookups.

## Complexity Analysis

Time Complexity: O(n), where n is the number of descriptions. We do one pass to
build the tree and one pass over the node map (also O(n) distinct values) to find
the root.

Space Complexity: O(n) for the node map, the children set, and the tree nodes
themselves.

## Edge Cases

- **Single node tree (no descriptions never happens, but a single edge):** With
the smallest valid input (one description), two nodes are created and one is
clearly the root — the helper still creates both correctly. The constraints
guarantee `descriptions.length >= 1`, so the map is never empty.
- **A node appears as a child before it appears as a parent (or vice versa):**
Because the helper creates nodes lazily and reuses them, the order in which
values are encountered does not matter. This is the key correctness concern.
- **Reusing the same node object:** If you accidentally create a fresh node each
time instead of reusing from the map, subtrees get dropped. The map prevents
this.
- **Distinguishing left vs right child:** Mixing up `isLeft == 1` (left) and
`isLeft == 0` (right) silently produces a wrong-but-valid-looking tree, so the
conditional must be exact.
65 changes: 65 additions & 0 deletions problems/2196-create-binary-tree-from-descriptions/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
number: "2196"
frontend_id: "2196"
title: "Create Binary Tree From Descriptions"
slug: "create-binary-tree-from-descriptions"
difficulty: "Medium"
topics:
- "Array"
- "Hash Table"
- "Tree"
- "Binary Tree"
acceptance_rate: 8231.3
is_premium: false
created_at: "2026-06-07T05:05:23.400176+00:00"
fetched_at: "2026-06-07T05:05:23.400176+00:00"
link: "https://leetcode.com/problems/create-binary-tree-from-descriptions/"
date: "2026-06-07"
---

# 2196. Create Binary Tree From Descriptions

You are given a 2D integer array `descriptions` where `descriptions[i] = [parenti, childi, isLefti]` indicates that `parenti` is the **parent** of `childi` in a **binary** tree of **unique** values. Furthermore,

* If `isLefti == 1`, then `childi` is the left child of `parenti`.
* If `isLefti == 0`, then `childi` is the right child of `parenti`.



Construct the binary tree described by `descriptions` and return _its**root**_.

The test cases will be generated such that the binary tree is **valid**.



**Example 1:**

![](https://assets.leetcode.com/uploads/2022/02/09/example1drawio.png)


**Input:** descriptions = [[20,15,1],[20,17,0],[50,20,1],[50,80,0],[80,19,1]]
**Output:** [50,20,80,15,17,19]
**Explanation:** The root node is the node with value 50 since it has no parent.
The resulting binary tree is shown in the diagram.


**Example 2:**

![](https://assets.leetcode.com/uploads/2022/02/09/example2drawio.png)


**Input:** descriptions = [[1,2,1],[2,3,0],[3,4,1]]
**Output:** [1,2,null,null,3,4]
**Explanation:** The root node is the node with value 1 since it has no parent.
The resulting binary tree is shown in the diagram.




**Constraints:**

* `1 <= descriptions.length <= 104`
* `descriptions[i].length == 3`
* `1 <= parenti, childi <= 105`
* `0 <= isLefti <= 1`
* The binary tree described by `descriptions` is valid.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

// Create Binary Tree From Descriptions
//
// Approach: hash map from value -> *TreeNode. Each description is an edge
// (parent, child, isLeft). We lazily create a single node per value and reuse it
// so every reference points to the same object. We track which values appear as
// children; the one value that never appears as a child is the root.
//
// Time: O(n) over the descriptions.
// Space: O(n) for the node map and children set.

// TreeNode is a binary tree node.
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}

func createBinaryTree(descriptions [][]int) *TreeNode {
nodes := make(map[int]*TreeNode)
children := make(map[int]bool)

// getNode returns the node for val, creating it on first use.
getNode := func(val int) *TreeNode {
if n, ok := nodes[val]; ok {
return n
}
n := &TreeNode{Val: val}
nodes[val] = n
return n
}

for _, d := range descriptions {
parentVal, childVal, isLeft := d[0], d[1], d[2]
parent := getNode(parentVal)
child := getNode(childVal)
if isLeft == 1 {
parent.Left = child
} else {
parent.Right = child
}
children[childVal] = true
}

// The root is the only value that is never a child.
for val, node := range nodes {
if !children[val] {
return node
}
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package main

import (
"sort"
"testing"
)

// levelOrder serializes a binary tree into the LeetCode-style level-order slice,
// using nil to mark absent children (trailing nils trimmed), so trees can be
// compared structurally.
func levelOrder(root *TreeNode) []interface{} {
var out []interface{}
if root == nil {
return out
}
queue := []*TreeNode{root}
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
if node == nil {
out = append(out, nil)
continue
}
out = append(out, node.Val)
queue = append(queue, node.Left, node.Right)
}
// Trim trailing nils to match LeetCode's compact representation.
for len(out) > 0 && out[len(out)-1] == nil {
out = out[:len(out)-1]
}
return out
}

func equalSlices(a, b []interface{}) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

// collectVals gathers all node values in the tree (order independent) so we can
// confirm structural integrity beyond just the root.
func collectVals(root *TreeNode) []int {
var vals []int
var dfs func(*TreeNode)
dfs = func(n *TreeNode) {
if n == nil {
return
}
vals = append(vals, n.Val)
dfs(n.Left)
dfs(n.Right)
}
dfs(root)
sort.Ints(vals)
return vals
}

func TestSolution(t *testing.T) {
tests := []struct {
name string
descriptions [][]int
wantLevel []interface{} // expected level-order serialization
wantVals []int // expected sorted set of values
}{
{
name: "example 1: standard tree rooted at 50",
descriptions: [][]int{{20, 15, 1}, {20, 17, 0}, {50, 20, 1}, {50, 80, 0}, {80, 19, 1}},
wantLevel: []interface{}{50, 20, 80, 15, 17, 19},
wantVals: []int{15, 17, 19, 20, 50, 80},
},
{
name: "example 2: left-leaning chain rooted at 1",
descriptions: [][]int{{1, 2, 1}, {2, 3, 0}, {3, 4, 1}},
wantLevel: []interface{}{1, 2, nil, nil, 3, 4},
wantVals: []int{1, 2, 3, 4},
},
{
name: "edge case: single edge, parent is root with one left child",
descriptions: [][]int{{1, 2, 1}},
wantLevel: []interface{}{1, 2},
wantVals: []int{1, 2},
},
{
name: "edge case: single edge, right child only",
descriptions: [][]int{{10, 5, 0}},
wantLevel: []interface{}{10, nil, 5},
wantVals: []int{5, 10},
},
{
name: "edge case: descriptions out of order (child seen before its own edge)",
descriptions: [][]int{{80, 19, 1}, {50, 80, 0}, {50, 20, 1}, {20, 15, 1}, {20, 17, 0}},
wantLevel: []interface{}{50, 20, 80, 15, 17, 19},
wantVals: []int{15, 17, 19, 20, 50, 80},
},
{
name: "edge case: right-leaning chain",
descriptions: [][]int{{1, 2, 0}, {2, 3, 0}},
wantLevel: []interface{}{1, nil, 2, nil, 3},
wantVals: []int{1, 2, 3},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := createBinaryTree(tt.descriptions)
got := levelOrder(root)
if !equalSlices(got, tt.wantLevel) {
t.Errorf("level order = %v, want %v", got, tt.wantLevel)
}
if gotVals := collectVals(root); !equalIntSlices(gotVals, tt.wantVals) {
t.Errorf("values = %v, want %v", gotVals, tt.wantVals)
}
})
}
}

func equalIntSlices(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}