diff --git a/problems/2196-create-binary-tree-from-descriptions/analysis_daily_20260607.md b/problems/2196-create-binary-tree-from-descriptions/analysis_daily_20260607.md new file mode 100644 index 0000000..a0f013b --- /dev/null +++ b/problems/2196-create-binary-tree-from-descriptions/analysis_daily_20260607.md @@ -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. diff --git a/problems/2196-create-binary-tree-from-descriptions/problem.md b/problems/2196-create-binary-tree-from-descriptions/problem.md new file mode 100644 index 0000000..b4c4000 --- /dev/null +++ b/problems/2196-create-binary-tree-from-descriptions/problem.md @@ -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. diff --git a/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607.go b/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607.go new file mode 100644 index 0000000..58734c7 --- /dev/null +++ b/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607.go @@ -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 +} diff --git a/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607_test.go b/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607_test.go new file mode 100644 index 0000000..e78ed79 --- /dev/null +++ b/problems/2196-create-binary-tree-from-descriptions/solution_daily_20260607_test.go @@ -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 +}