Skip to content

Commit 6d7e427

Browse files
Add 'Construct Binary Tree from Preorder and Inorder Traversal'
1 parent 6546456 commit 6d7e427

5 files changed

+125
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ A collection of LeetCode solutions
1616

1717
[Coin Change](./src/coin_change.py)
1818

19+
[Construct Binary Tree from Preorder and Inorder Traversal](./src/construct_binary_tree_from_preorder_and_inorder_traversal.py)
20+
1921
[Contains Duplicate](./src/contains_duplicate.py)
2022

2123
[Find Median from Data Stream](./src/find_median_from_data_stream.py)

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
- [ ] Add a *divide and conquer* approach to 'Merge k Sorted Lists'. This approach has O(nlog n) time complexity, but O(1) space complexity.
1414
- [ ] Add self-balancing BST approach to 'Find Median from Data Stream'
1515
- [ ] Add iterative without parent pointers approach to 'Lowest Common Ancestor of a Binary Tree'
16+
- [ ] Add recursive approach to 'Binary Tree Right Side View'
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""
2+
105. Construct Binary Tree from Preorder and Inorder Traversal
3+
4+
https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
5+
6+
NOTES
7+
* Good primer for 'Serialize and Deserialize Binary Tree'. Recall that,
8+
9+
>No single traversal order (pre-order, post-order, or in-order) uniquely
10+
identifies the structure of a tree...
11+
12+
If the binary tree was guaranteed to be complete, meaning all levels except
13+
possibly the last are fully filled and the last level is filled from left to
14+
right, this problem would be trivial. Recall that a heap, which is a complete
15+
binary tree, can be represented as a zero-based array where a node at position
16+
i has children at positions 2*i+1 and 2*i+2. So, all we would need to do is
17+
construct the tree by iterating through the pre-order array. Unfortunately,
18+
it's not that simple, since the tree is *not* guaranteed to be a complete tree
19+
and therefore we must account for gaps.
20+
21+
Using the definitions of pre-order and in-order traversal, we can make two key
22+
observations:
23+
24+
1. The root of the tree is located at `preorder[0]`.
25+
2. The subarrays created by splitting `inorder` at root represents the left
26+
and right subtrees of the root of the tree.
27+
28+
From this, a recursive logic emerges: the subtrees rooted at `preorder[i]` are
29+
created by splitting `inorder` at `preorder[i]`.
30+
31+
It should be noted that the range for the subtrees is reduced logarithmically
32+
for each recursive call.
33+
34+
One of the major "gotchas" in this problem is that we cannot assume that a node
35+
in `preorder` is the left or right node. For this reason, we only increment the
36+
pointer to the index of `preorder` if a node can be created (i.e., the
37+
recursive function does not return None).
38+
39+
For example, consider the following tree:
40+
41+
1
42+
43+
\
44+
2
45+
46+
47+
Pre-order = [1, 2]
48+
In-order = [1, 2]
49+
50+
The only way to signify programmatically that 2 is the right node of 1 and not
51+
the left node is to only increment the index of `preorder` if the result of
52+
calling our recursive function does not return None.
53+
"""
54+
55+
from src.classes import TreeNode
56+
57+
58+
class Solution:
59+
def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None:
60+
# Create a mapping of value -> index for `inorder` (O(1) lookups).
61+
d: dict[int, int] = {}
62+
for i, v in enumerate(inorder):
63+
d[v] = i
64+
65+
preorder_idx = 0
66+
67+
def _buildTree(left: int, right: int) -> TreeNode | None:
68+
"""
69+
Helper function for `buildTree()`.
70+
"""
71+
nonlocal preorder_idx
72+
73+
if left > right:
74+
return None
75+
76+
preorder_val = preorder[preorder_idx]
77+
node = TreeNode(preorder_val)
78+
79+
preorder_idx += 1
80+
81+
node.left = _buildTree(left, d[preorder_val] - 1)
82+
node.right = _buildTree(d[preorder_val] + 1, right)
83+
84+
return node
85+
86+
return _buildTree(0, len(preorder) - 1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
105. Construct Binary Tree from Preorder and Inorder Traversal
3+
4+
https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
5+
"""
6+
7+
from unittest import TestCase
8+
9+
from src.construct_binary_tree_from_preorder_and_inorder_traversal import Solution
10+
from tests.utils import is_valid_tree
11+
12+
13+
class TestSolution(TestCase):
14+
def test_1(self):
15+
exp = [3, 9, 20, None, None, 15, 7]
16+
is_valid_tree(Solution().buildTree(preorder=[3, 9, 20, 15, 7], inorder=[9, 3, 15, 20, 7]), 0, exp)
17+
18+
def test_2(self):
19+
exp = [-1]
20+
is_valid_tree(Solution().buildTree(preorder=[-1], inorder=[-1]), 0, exp)
21+
22+
def test_3(self):
23+
exp = [1, None, 2]
24+
is_valid_tree(Solution().buildTree(preorder=[1, 2], inorder=[1, 2]), 0, exp)

tests/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,15 @@ def create_bfs_list_from_binary_tree(*, root: TreeNode | None, values_only: bool
129129
q.append(curr.right)
130130

131131
return l
132+
133+
134+
def is_valid_tree(root: TreeNode | None, i: int, exp: list[T | None]) -> None:
135+
"""
136+
Given a list of values in pre-order traversal, assert that the tree is
137+
valid. Gaps in the tree are denoted by None.
138+
"""
139+
if not root:
140+
return None
141+
assert root.val == exp[i]
142+
is_valid_tree(root.left, 2 * i + 1, exp)
143+
is_valid_tree(root.right, 2 * i + 2, exp)

0 commit comments

Comments
 (0)