Skip to content

Commit a98cb7a

Browse files
committed
8/28 - 8/30: Binary trees
- kth_node_in_tree.py - is_tree_balanced.py - is_tree_symmetric.py - lowest_common_ancestor.py - lowest_common_ancestor_with_parent.py - path_sum.py - sum_root_to_leaf.py - successor_in_tree.py - tree_connect_leaves.py Plus some small changes to binary tree libraries: - binary_tree_node.py - binary_tree_with_parent_prototype.py - test_framework/binary_tree_utils.py
1 parent 5bc94d0 commit a98cb7a

13 files changed

+287
-29
lines changed

elements-of-programming-interviews/problem_mapping.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ problem_mapping = {
10101010
"total": 3852
10111011
},
10121012
"Python: is_tree_balanced.py": {
1013-
"passed": 0,
1013+
"passed": 3852,
10141014
"total": 3852
10151015
}
10161016
},
@@ -1024,7 +1024,7 @@ problem_mapping = {
10241024
"total": 3852
10251025
},
10261026
"Python: is_tree_symmetric.py": {
1027-
"passed": 0,
1027+
"passed": 3852,
10281028
"total": 3852
10291029
}
10301030
},
@@ -1038,7 +1038,7 @@ problem_mapping = {
10381038
"total": 948
10391039
},
10401040
"Python: lowest_common_ancestor.py": {
1041-
"passed": 0,
1041+
"passed": 948,
10421042
"total": 948
10431043
}
10441044
},
@@ -1052,7 +1052,7 @@ problem_mapping = {
10521052
"total": 948
10531053
},
10541054
"Python: lowest_common_ancestor_with_parent.py": {
1055-
"passed": 0,
1055+
"passed": 948,
10561056
"total": 948
10571057
}
10581058
},
@@ -1066,7 +1066,7 @@ problem_mapping = {
10661066
"total": 3849
10671067
},
10681068
"Python: sum_root_to_leaf.py": {
1069-
"passed": 0,
1069+
"passed": 3849,
10701070
"total": 3849
10711071
}
10721072
},
@@ -1080,7 +1080,7 @@ problem_mapping = {
10801080
"total": 7690
10811081
},
10821082
"Python: path_sum.py": {
1083-
"passed": 0,
1083+
"passed": 7690,
10841084
"total": 7690
10851085
}
10861086
},
@@ -1108,7 +1108,7 @@ problem_mapping = {
11081108
"total": 3851
11091109
},
11101110
"Python: kth_node_in_tree.py": {
1111-
"passed": 0,
1111+
"passed": 3851,
11121112
"total": 3851
11131113
}
11141114
},
@@ -1122,7 +1122,7 @@ problem_mapping = {
11221122
"total": 948
11231123
},
11241124
"Python: successor_in_tree.py": {
1125-
"passed": 0,
1125+
"passed": 948,
11261126
"total": 948
11271127
}
11281128
},
@@ -1178,7 +1178,7 @@ problem_mapping = {
11781178
"total": 3852
11791179
},
11801180
"Python: tree_connect_leaves.py": {
1181-
"passed": 0,
1181+
"passed": 3852,
11821182
"total": 3852
11831183
}
11841184
},

elements-of-programming-interviews/python/binary_tree_node.py

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def __eq__(self, other):
1212
return equal_binary_trees(self, other)
1313

1414
def __repr__(self):
15+
# return f"BinaryTreeNode({self.data})"
1516
return str(binary_tree_to_string(self))
1617

1718
def __str__(self):

elements-of-programming-interviews/python/binary_tree_with_parent_prototype.py

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def __init__(self, data=None, left=None, right=None, parent=None):
99
self.parent = parent
1010

1111
def __repr__(self):
12+
# return f"BTNode({self.data})"
1213
return str(binary_tree_to_string(self))
1314

1415
def __str__(self):

elements-of-programming-interviews/python/is_tree_balanced.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
11
from binary_tree_node import BinaryTreeNode
22
from test_framework import generic_test
33

4+
"""
5+
Recursive solution:
6+
height of a tree is 1 plus each of its subtrees (unless they're both empty)
7+
recursively calculate the height and difference
8+
9+
"""
10+
11+
12+
def display(node, prefix="", margin=0):
13+
print(f"{' '*margin}| {prefix + ':' if prefix else ''} {node.data}")
14+
if node.right:
15+
display(node.right, 'R', margin+2)
16+
if node.left:
17+
display(node.left, 'L', margin+2)
18+
419

520
def is_balanced_binary_tree(tree: BinaryTreeNode) -> bool:
6-
# TODO - you fill in here.
7-
return True
21+
# print("")
22+
# display(tree)
23+
24+
def balance_height(node):
25+
if not node:
26+
return -1, True
27+
lheight, lbalance = balance_height(node.left)
28+
rheight, rbalance = balance_height(node.right)
29+
30+
height = max(lheight, rheight) + 1
31+
balanced = lbalance and rbalance and abs(lheight - rheight) < 2
32+
return height, balanced
33+
34+
_, balanced = balance_height(tree)
35+
36+
return balanced
837

938

1039
if __name__ == '__main__':

elements-of-programming-interviews/python/is_tree_symmetric.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
from binary_tree_node import BinaryTreeNode
22
from test_framework import generic_test
33

4+
"""
5+
Suppose we went through the left and right trees together:
6+
root.left must equal root.right
7+
root.left.left must equal root.right.right
8+
root.left.right must equal root.right.left
9+
10+
So we recursively implement this; base case is if one node is none, both must be.
11+
"""
12+
413

514
def is_symmetric(tree: BinaryTreeNode) -> bool:
6-
# TODO - you fill in here.
7-
return True
15+
if not tree:
16+
return True
17+
18+
def compare(left_subtree, right_subtree):
19+
if None in [left_subtree, right_subtree]:
20+
return left_subtree == right_subtree
21+
symmetric = left_subtree.data == right_subtree.data
22+
symmetric &= compare(left_subtree.left, right_subtree.right)
23+
symmetric &= compare(left_subtree.right, right_subtree.left)
24+
return symmetric
25+
26+
return compare(tree.left, tree.right)
827

928

1029
if __name__ == '__main__':

elements-of-programming-interviews/python/kth_node_in_tree.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,36 @@ def __init__(self, data=None, left=None, right=None, size=None):
1414
self.size = size
1515

1616

17+
"""
18+
- Inorder traversal is LNR. So before we process the current node, we have to process the entire left subtree.
19+
- going left means "we will process fewer nodes before reaching this node"
20+
- going right mean "we commit to processing this many nodes."
21+
22+
At each node:
23+
if k == left subtree size + 1, current node is kth.
24+
if k < left subtree size + 1, go left.
25+
if k > left subtree's size + 1, go right and subtract left subtree's size plus one (for root) from k.
26+
27+
28+
edge cases: tree empty, k is larger than whole tree size (assume this won't happen for now)
29+
"""
30+
31+
1732
def find_kth_node_binary_tree(tree: BinaryTreeNode,
1833
k: int) -> Optional[BinaryTreeNode]:
19-
# TODO - you fill in here.
20-
return None
34+
if (not tree) or k > tree.size:
35+
return None
36+
37+
def find_kth(root, remaining):
38+
traversable = (root.left.size+1 if root.left else 1)
39+
if remaining == traversable:
40+
return root
41+
if root.left and traversable > remaining:
42+
return find_kth(root.left, remaining)
43+
else: # (not root.left) or traversable < remaining
44+
return find_kth(root.right, remaining-traversable)
45+
46+
return find_kth(tree, k)
2147

2248

2349
@enable_executor_hook

elements-of-programming-interviews/python/lowest_common_ancestor.py

+58-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,66 @@
88
from test_framework.test_utils import enable_executor_hook
99

1010

11+
def get_path(root, node, path):
12+
if not root:
13+
return False
14+
path.append(root)
15+
if root == node:
16+
return True
17+
18+
if root.left:
19+
if get_path(root.left, node, path):
20+
return True
21+
if root.right:
22+
if get_path(root.right, node, path):
23+
return True
24+
path.pop()
25+
return False
26+
27+
28+
def lca_Oh_space(tree: BinaryTreeNode, node0: BinaryTreeNode,
29+
node1: BinaryTreeNode) -> Optional[BinaryTreeNode]:
30+
node0_path = []
31+
get_path(tree, node0, node0_path)
32+
node1_path = []
33+
get_path(tree, node1, node1_path)
34+
35+
solution = tree
36+
for n0, n1 in zip(node0_path, node1_path):
37+
if n0 is n1:
38+
solution = n0
39+
else:
40+
break
41+
42+
return solution
43+
44+
1145
def lca(tree: BinaryTreeNode, node0: BinaryTreeNode,
1246
node1: BinaryTreeNode) -> Optional[BinaryTreeNode]:
13-
# TODO - you fill in here.
14-
return None
47+
best_lca = tree
48+
best_lca_distance = 0
49+
50+
def nodes_in_tree(root, depth):
51+
if not root:
52+
return False, False
53+
54+
node0_left, node1_left = nodes_in_tree(root.left, depth+1)
55+
node0_right, node1_right = nodes_in_tree(root.right, depth+1)
56+
57+
node0_exists = (root is node0) or node0_left or node0_right
58+
node1_exists = (root is node1) or node1_left or node1_right
59+
60+
nonlocal best_lca_distance
61+
nonlocal best_lca
62+
if (node0_exists and node1_exists) and best_lca_distance < depth:
63+
best_lca_distance = depth
64+
best_lca = root
65+
66+
return node0_exists, node1_exists
67+
68+
node0_exists, node1_exists = nodes_in_tree(tree, 0)
69+
70+
return best_lca if (node0_exists and node1_exists) else None
1571

1672

1773
@enable_executor_hook

elements-of-programming-interviews/python/lowest_common_ancestor_with_parent.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,34 @@
77
from test_framework.test_failure import TestFailure
88
from test_framework.test_utils import enable_executor_hook
99

10+
"""
11+
Get depth of each node by tracing to root. Then advance the longer to meet the shorter's depth,
12+
then go one by one until they reach the first common node, which is LCA.
13+
"""
14+
15+
16+
def get_depth(node):
17+
depth = -1
18+
while node is not None:
19+
node = node.parent
20+
depth += 1
21+
return depth
22+
1023

1124
def lca(node0: BinaryTreeNode,
1225
node1: BinaryTreeNode) -> Optional[BinaryTreeNode]:
13-
# TODO - you fill in here.
14-
return None
26+
n0_depth, n1_depth = get_depth(node0), get_depth(node1)
27+
if n0_depth != n1_depth:
28+
node0, node1 = (node0, node1) if n0_depth > n1_depth else (node1, node0)
29+
n0_depth, n1_depth = (n0_depth, n1_depth) if n0_depth > n1_depth else (n1_depth, n0_depth)
30+
while n0_depth > n1_depth:
31+
node0 = node0.parent
32+
n0_depth -= 1
33+
34+
while node0 is not node1:
35+
node0 = node0.parent
36+
node1 = node1.parent
37+
return node0
1538

1639

1740
@enable_executor_hook

elements-of-programming-interviews/python/path_sum.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33

44

55
def has_path_sum(tree: BinaryTreeNode, remaining_weight: int) -> bool:
6-
# TODO - you fill in here.
7-
return True
6+
def recurse(node, remaining):
7+
if not node:
8+
return False
9+
10+
remaining -= node.data
11+
if not node.left and not node.right:
12+
return remaining == 0
13+
return recurse(node.left, remaining) or recurse(node.right, remaining)
14+
15+
return recurse(tree, remaining_weight)
816

917

1018
if __name__ == '__main__':

0 commit comments

Comments
 (0)