Trees¶
Python implementation ā 12 questions covering core concepts and interview patterns.
TREES ā Core Concepts & Interview Questions
"""
TREES ā Core Concepts & Interview Questions
============================================
Core Concepts:
āāāāāāāāāāāāāā
⢠Binary Tree: Each node has at most 2 children (left, right)
⢠Binary Search Tree (BST): left < root < right ā enables O(log n) search
⢠Tree traversals: Preorder (root-L-R), Inorder (L-root-R), Postorder (L-R-root)
⢠BFS (Level Order): Queue-based, processes level by level
⢠DFS: Stack-based (or recursive), goes deep before wide
⢠Balanced trees: AVL, Red-Black ā height = O(log n)
⢠Complete vs Full vs Perfect binary trees
Common Patterns:
āāāāāāāāāāāāāāāā
⢠Recursive divide and conquer
⢠Level-order traversal (BFS with queue)
⢠Lowest Common Ancestor (LCA)
⢠Serialize / Deserialize
⢠BST validation (inorder traversal check)
⢠Path sum problems
"""
from typing import List, Optional, Tuple
from collections import deque
class TreeNode:
"""Binary tree node."""
def __init__(self, val: int = 0, left: Optional['TreeNode'] = None, right: Optional['TreeNode'] = None):
self.val = val
self.left = left
self.right = right
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 1: Binary Tree Traversals (Recursive & Iterative)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def inorder_traversal(root: Optional[TreeNode]) -> List[int]:
"""
QUESTION:
āāāāāāāāā
Perform inorder traversal of a binary tree. For BST, this gives
sorted order.
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Recursive: left ā root ā right ā O(n) time, O(h) space (stack)
2. Iterative: Use stack to simulate recursion
- Go left as far as possible, pushing nodes
- Pop, visit, then go right
3. Morris Traversal: temporarily thread each left subtree's rightmost
node back to its ancestor, giving O(1) extra space (it mutates the
tree while running, then restores it)
4. Prefer the iterative form in Python for deep trees: recursion
depth is capped at ~1000 by default, and a skewed tree of 10ā“
nodes raises RecursionError
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node once
Space: O(h) ā Stack height = tree height (h = n for skewed tree)
"""
result = []
stack = []
current = root
while current or stack:
# Go as far left as possible
while current:
stack.append(current)
current = current.left
# Pop and visit
current = stack.pop()
result.append(current.val)
# Go right
current = current.right
return result
def preorder_traversal(root: Optional[TreeNode]) -> List[int]:
"""
Preorder: root ā left ā right. Used for copying/tree serialization.
Time: O(n), Space: O(h)
"""
if not root:
return []
result = []
stack = [root]
while stack:
node = stack.pop()
result.append(node.val)
# Push right first so left is processed first (LIFO)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return result
def postorder_traversal(root: Optional[TreeNode]) -> List[int]:
"""
Postorder: left ā right ā root. Used for tree deletion.
Time: O(n), Space: O(h)
"""
if not root:
return []
result = []
stack = [(root, False)]
while stack:
node, visited = stack.pop()
if visited:
result.append(node.val)
else:
stack.append((node, True))
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, False))
return result
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 2: Level Order Traversal (BFS)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def level_order(root: Optional[TreeNode]) -> List[List[int]]:
"""
QUESTION:
āāāāāāāāā
Return the level order traversal of a binary tree (left to right,
level by level).
Example:
Input: Tree: 3 ā (9, 20 ā (15, 7))
Output: [[3], [9, 20], [15, 7]]
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Use a queue for BFS
2. Process one level at a time: get queue size, process that many nodes
3. For each node, add its children to the queue
4. The "size" technique ensures we know level boundaries
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node once
Space: O(w) ā Queue width = max level width (up to n/2)
"""
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 3: Validate Binary Search Tree
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def is_valid_bst(root: Optional[TreeNode]) -> bool:
"""
QUESTION:
āāāāāāāāā
Given the root of a binary tree, determine if it is a valid BST.
A valid BST: left subtree < root < right subtree (recursively).
Example:
Input: 2 ā (1, 3)
Output: true
Input: 5 ā (1, 4 ā (3, 6))
Output: false
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Naive: Check if left < root and right > root ā WRONG!
Need ALL nodes in left subtree < root, not just immediate child
2. Approach 1: Inorder traversal should be strictly increasing
(for BST, inorder gives sorted order)
3. Approach 2: Recursive with min/max bounds
- For each node, keep a valid range (low, high)
- Left child must be in (low, root.val)
- Right child must be in (root.val, high)
4. Bounds start at ±infinity, not at INT_MIN/INT_MAX: in Java a node
holding Integer.MIN_VALUE would wrongly fail. Use Long bounds or
null-able bounds there.
5. Duplicates: this treats them as invalid (strict <). Ask which side
duplicates go on if the interviewer allows them.
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node once
Space: O(h) ā Recursion stack
"""
def validate(node: Optional[TreeNode], low: float, high: float) -> bool:
if not node:
return True
if node.val <= low or node.val >= high:
return False
return (validate(node.left, low, node.val) and
validate(node.right, node.val, high))
return validate(root, float('-inf'), float('inf'))
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 4: Maximum Depth of Binary Tree
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def max_depth(root: Optional[TreeNode]) -> int:
"""
QUESTION:
āāāāāāāāā
Given the root, find its maximum depth (number of nodes along the
longest path from root to farthest leaf).
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Recursive: max_depth = 1 + max(left_depth, right_depth)
2. This is a classic divide-and-conquer problem
3. Bottom-up: compute subtree depths, combine at parent
4. Top-down: track depth while traversing, update global max
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node
Space: O(h) ā Recursion stack
"""
if not root:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 5: Lowest Common Ancestor (BST)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def lowest_common_ancestor_bst(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> Optional[TreeNode]:
"""
QUESTION:
āāāāāāāāā
Given a BST, find the lowest common ancestor of two nodes.
Example:
Input: BST root=6, p=2, q=8
Output: 6
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. BST property: left < root < right
2. If p and q are on different sides of root ā root is LCA
3. If both are smaller ā LCA is in left subtree
4. If both are larger ā LCA is in right subtree
5. This eliminates the need to check ancestors explicitly
COMPLEXITY:
āāāāāāāāāā
Time: O(h) ā Height of tree, O(log n) for balanced BST
Space: O(1) ā Iterative, no stack
"""
current = root
while current:
if p.val < current.val and q.val < current.val:
current = current.left
elif p.val > current.val and q.val > current.val:
current = current.right
else:
return current # Split point ā LCA found
return None
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 6: Lowest Common Ancestor (Binary Tree)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def lowest_common_ancestor(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> Optional[TreeNode]:
"""
QUESTION:
āāāāāāāāā
Find LCA in a general binary tree (not necessarily BST).
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Recursive DFS:
- If current node is p or q, return it
- Search left and right subtrees
- If both sides return non-None ā current is LCA
- If only one side returns non-None ā propagate that up
2. This is a bottom-up approach ā finds the deepest common ancestor
3. Assumes both p and q are in the tree. If one may be missing, this
returns the other node (wrong); count how many of p, q were
actually found and return None unless both were.
4. If nodes have parent pointers: walk up from p recording ancestors
in a set, then walk up from q (or use the two-pointer trick from
linked-list intersection).
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Worst case visit all nodes
Space: O(h) ā Recursion stack
"""
if not root or root is p or root is q:
return root
left = lowest_common_ancestor(root.left, p, q)
right = lowest_common_ancestor(root.right, p, q)
if left and right:
return root # p and q are in different subtrees
return left or right # Both in one subtree
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 7: Serialize and Deserialize Binary Tree
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def serialize(root: Optional[TreeNode]) -> str:
"""
QUESTION:
āāāāāāāāā
Design an algorithm to serialize a binary tree to a string and
deserialize it back.
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Use preorder traversal with a delimiter (',') and null marker ('#')
2. Preorder + explicit null markers uniquely encodes the structure
(without null markers you need two traversals, e.g. preorder +
inorder, and distinct values)
3. Deserialize: consume tokens from an iterator in the same preorder;
each '#' ends a branch
4. Alternative: Level order BFS with nulls (LeetCode's format)
5. For a BST, preorder WITHOUT nulls is enough: rebuild using
(low, high) bounds ā a more compact encoding
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node
Space: O(n) ā String representation
"""
def dfs(node: Optional[TreeNode]) -> None:
if not node:
result.append('#')
return
result.append(str(node.val))
dfs(node.left)
dfs(node.right)
result = []
dfs(root)
return ','.join(result)
def deserialize(data: str) -> Optional[TreeNode]:
"""Deserialize a string back to a binary tree."""
def dfs() -> Optional[TreeNode]:
val = next(values)
if val == '#':
return None
node = TreeNode(int(val))
node.left = dfs()
node.right = dfs()
return node
values = iter(data.split(','))
return dfs()
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 8: Binary Tree Right Side View
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def right_side_view(root: Optional[TreeNode]) -> List[int]:
"""
QUESTION:
āāāāāāāāā
Given a binary tree, imagine you're standing on the right side of it.
Return the values of the nodes you can see (ordered top to bottom).
Example:
Input: 1 ā (2 ā 5, 3 ā 4)
Output: [1, 3, 4]
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. BFS level order: the last node at each level is the rightmost
2. Or DFS: track level, first time visiting a level at a certain
recursion order
3. For right side view, process right before left in DFS
4. This ensures the rightmost node of each level is seen first
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node
Space: O(h) ā Recursion stack
"""
result = []
def dfs(node: Optional[TreeNode], level: int) -> None:
if not node:
return
if level == len(result):
result.append(node.val) # First visit to this level
dfs(node.right, level + 1) # Process right first
dfs(node.left, level + 1)
dfs(root, 0)
return result
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 9: Kth Smallest Element in BST
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def kth_smallest(root: Optional[TreeNode], k: int) -> int:
"""
QUESTION:
āāāāāāāāā
Find the kth smallest element in a BST (1-indexed).
Example:
Input: root = 3 ā (1 ā None ā 2, 4), k = 1
Output: 1
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Inorder traversal of BST gives sorted order
2. Iterative inorder: stop after k elements
3. This is O(h + k) because we stop early
4. Follow-up "the BST is modified often and kth is queried often":
store subtree sizes in each node (order-statistic tree), then
walk down in O(h): go left if k <= size(left), etc.
COMPLEXITY:
āāāāāāāāāā
Time: O(h + k) ā h to reach smallest, k to find kth
Space: O(h) ā Stack
"""
stack = []
current = root
while current or stack:
while current:
stack.append(current)
current = current.left
current = stack.pop()
k -= 1
if k == 0:
return current.val
current = current.right
return -1 # k > tree size
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 10: Diameter of Binary Tree
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def diameter_of_binary_tree(root: Optional[TreeNode]) -> int:
"""
QUESTION:
āāāāāāāāā
Given a binary tree, find the length of the longest path between
any two nodes (diameter). The path may or may not pass through root.
Example:
Input: 1 ā (2 ā 4 ā 5, 3)
Output: 3 (4 ā 2 ā 1 ā 3, length = 3 edges)
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. For each node, diameter through that node = left_depth + right_depth
2. We need max of (left_depth + right_depth) across all nodes
3. Post-order traversal: compute depths bottom-up
4. At each node: update global max with left+right, return 1+max(left,right)
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Visit each node once
Space: O(h) ā Recursion stack
"""
diameter = 0
def depth(node: Optional[TreeNode]) -> int:
nonlocal diameter
if not node:
return 0
left = depth(node.left)
right = depth(node.right)
# Update diameter: path through this node
diameter = max(diameter, left + right)
# Return height of this subtree
return 1 + max(left, right)
depth(root)
return diameter
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 11: Binary Tree Maximum Path Sum
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def max_path_sum(root: Optional[TreeNode]) -> int:
"""
QUESTION:
āāāāāāāāā
A path is any sequence of nodes connected by edges, each node used at
most once; it need not pass through the root. Return the maximum sum
of node values over all non-empty paths. Values may be negative.
Example:
Input: [-10, 9, 20, None, None, 15, 7]
Output: 42 (15 ā 20 ā 7)
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. Same skeleton as Diameter (Q10): every path has a single highest
node where it "bends". At that node the path is
node.val + best downward chain on the left + on the right.
2. A recursive helper returns the best DOWNWARD chain starting at the
node (it can extend only one side to its parent):
gain(node) = node.val + max(0, gain(left), gain(right))
and clamps child gains at 0: a negative chain is dropped, not used.
3. While recursing, update a global answer with the bent path
node.val + max(0, gain(left)) + max(0, gain(right))
4. Two values per node ā "what I return to my parent" vs "best path
that peaks here" ā is the reusable pattern (diameter, longest
univalue path, house robber III).
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Each node visited once
Space: O(h) ā Recursion stack
EDGE CASES:
āāāāāāāāāā
⢠All negative ā the single largest node (start the answer at -inf,
not 0)
⢠Single node ā its value
"""
best = float("-inf")
def gain(node: Optional[TreeNode]) -> int:
nonlocal best
if not node:
return 0
left = max(gain(node.left), 0) # Drop negative chains
right = max(gain(node.right), 0)
best = max(best, node.val + left + right) # Path peaking here
return node.val + max(left, right) # Chain for the parent
gain(root)
return best
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# QUESTION 12: Construct Binary Tree from Preorder and Inorder
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def build_tree_pre_in(preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
"""
QUESTION:
āāāāāāāāā
Given the preorder and inorder traversals of a binary tree with
DISTINCT values, reconstruct the tree.
Example:
preorder = [3, 9, 20, 15, 7], inorder = [9, 3, 15, 20, 7]
ā 3 ā (9, 20 ā (15, 7))
THOUGHT PROCESS:
āāāāāāāāāāāāāāāā
1. preorder[0] is the root. Its position in inorder splits inorder
into the left subtree (before it) and the right subtree (after).
2. The left subtree's size tells how many of the next preorder
entries belong to it; the rest belong to the right subtree.
3. Naive slicing + inorder.index() is O(n²). Fix both:
- Precompute value ā inorder index in a hash map
- Recurse on index ranges and consume preorder with a moving
pointer instead of slicing
4. Why distinct values: with duplicates the root's inorder position
is ambiguous and the tree is not unique.
5. Preorder + postorder alone is NOT enough in general (a node with a
single child could be left or right).
COMPLEXITY:
āāāāāāāāāā
Time: O(n) ā Each node created once, O(1) index lookups
Space: O(n) ā Index map, plus O(h) recursion
"""
index_of = {val: i for i, val in enumerate(inorder)}
pre_idx = 0
def build(lo: int, hi: int) -> Optional[TreeNode]:
"""Build the subtree whose inorder range is [lo, hi]."""
nonlocal pre_idx
if lo > hi:
return None
root_val = preorder[pre_idx]
pre_idx += 1
mid = index_of[root_val]
node = TreeNode(root_val)
node.left = build(lo, mid - 1) # Left first: preorder order
node.right = build(mid + 1, hi)
return node
return build(0, len(inorder) - 1)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# HELPER: Build tree from list (level-order)
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def build_tree(values: List[Optional[int]]) -> Optional[TreeNode]:
if not values:
return None
root = TreeNode(values[0])
queue = deque([root])
i = 1
while queue and i < len(values):
node = queue.popleft()
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
queue.append(node.left)
i += 1
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
queue.append(node.right)
i += 1
return root
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
# DEMO
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
def demo():
print("=" * 70)
print("TREES ā Interview Questions Demo")
print("=" * 70)
# Build sample tree: 3 ā (9, 20 ā (15, 7))
tree = build_tree([3, 9, 20, None, None, 15, 7])
# Q1
print("\n1ļøā£ Tree Traversals")
print("-" * 40)
print(f" Inorder: {inorder_traversal(tree)}")
print(f" Preorder: {preorder_traversal(tree)}")
print(f" Postorder: {postorder_traversal(tree)}")
# Q2
print("\n2ļøā£ Level Order Traversal")
print("-" * 40)
print(f" Level Order: {level_order(tree)}")
# Q3
print("\n3ļøā£ Validate BST")
print("-" * 40)
valid_bst = build_tree([2, 1, 3])
invalid_bst = build_tree([5, 1, 4, None, None, 3, 6])
print(f" [2,1,3] is BST: {is_valid_bst(valid_bst)}")
print(f" [5,1,4,null,null,3,6] is BST: {is_valid_bst(invalid_bst)}")
# Q4
print("\n4ļøā£ Maximum Depth")
print("-" * 40)
print(f" Max depth: {max_depth(tree)}")
# Q5
print("\n5ļøā£ LCA in BST")
print("-" * 40)
bst = build_tree([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5])
p = bst.left # 2
q = bst.right # 8
lca = lowest_common_ancestor_bst(bst, p, q)
print(f" BST: 6 ā (2, 8)")
print(f" LCA of 2 and 8: {lca.val}")
# Q6
print("\n6ļøā£ LCA in Binary Tree")
print("-" * 40)
lca_node = lowest_common_ancestor(tree, tree.left, tree.right)
print(f" LCA of 9 and 20: {lca_node.val}")
# Q7
print("\n7ļøā£ Serialize/Deserialize")
print("-" * 40)
serialized = serialize(tree)
deserialized = deserialize(serialized)
print(f" Serialized: {serialized}")
print(f" Deserialized inorder: {inorder_traversal(deserialized)}")
# Q8
print("\n8ļøā£ Right Side View")
print("-" * 40)
print(f" Right side view: {right_side_view(tree)}")
# Q9
print("\n9ļøā£ Kth Smallest in BST")
print("-" * 40)
print(f" 1st smallest in [2,1,3]: {kth_smallest(valid_bst, 1)}")
print(f" 2nd smallest in [2,1,3]: {kth_smallest(valid_bst, 2)}")
# Q10
print("\nš Diameter of Binary Tree")
print("-" * 40)
dia_tree = build_tree([1, 2, 3, 4, 5])
print(f" Tree: [1, 2, 3, 4, 5]")
print(f" Diameter: {diameter_of_binary_tree(dia_tree)}")
# Q11
print("\n1ļøā£1ļøā£ Binary Tree Maximum Path Sum")
print("-" * 40)
mps_tree = build_tree([-10, 9, 20, None, None, 15, 7])
print(f" Tree: [-10, 9, 20, null, null, 15, 7]")
print(f" Max path sum: {max_path_sum(mps_tree)}")
# Q12
print("\n1ļøā£2ļøā£ Construct Tree from Preorder + Inorder")
print("-" * 40)
rebuilt = build_tree_pre_in([3, 9, 20, 15, 7], [9, 3, 15, 20, 7])
print(f" preorder=[3, 9, 20, 15, 7], inorder=[9, 3, 15, 20, 7]")
print(f" Rebuilt level order: {level_order(rebuilt)}")
print("\n" + "=" * 70)
if __name__ == "__main__":
demo()