Vasu Patel logoVasu Patel
← Python DSA — Course
All chapters in this track
  1. 011. Complexity Analysis
  2. 022. Arrays & Strings
  3. 033. Hashing
  4. 044. Stacks & Queues
  5. 055. Linked Lists
  6. 066. Binary Search
  7. 077. Sorting
  8. 088. Recursion & Backtracking
  9. 099. Trees
  10. 1010. Heaps
  11. 1111. Graphs
  12. 1212. Dynamic Programming
  13. 1313. Tries
  14. 1414. Advanced Structures
  15. 15Appendix: CP Quick Reference

Chapter 9 of 15

9. Trees

Key Insight

95% of tree problems are solved with DFS (recursive post-order for bottom-up info, pre-order for top-down). Internalize: what info do I need from my children to answer the question at each node?

9.1 Node Definition

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

9.2 Traversals

# --- Recursive traversals ---
def inorder(root: TreeNode | None) -> list[int]:
    return inorder(root.left) + [root.val] + inorder(root.right) if root else []

def preorder(root: TreeNode | None) -> list[int]:
    return [root.val] + preorder(root.left) + preorder(root.right) if root else []

def postorder(root: TreeNode | None) -> list[int]:
    return postorder(root.left) + postorder(root.right) + [root.val] if root else []

# --- Iterative inorder (avoids recursion limit) ---
def inorder_iter(root: TreeNode | None) -> list[int]:
    stack, res = [], []
    cur = root
    while cur or stack:
        while cur:
            stack.append(cur); cur = cur.left
        cur = stack.pop()
        res.append(cur.val)
        cur = cur.right
    return res

# --- Level order (BFS) ---
from collections import deque
def level_order(root: TreeNode | None) -> list[list[int]]:
    if not root:
        return []
    q = deque([root])
    res = []
    while q:
        level = []
        for _ in range(len(q)):
            node = q.popleft()
            level.append(node.val)
            if node.left:  q.append(node.left)
            if node.right: q.append(node.right)
        res.append(level)
    return res

9.3 Core Tree Algorithms

# --- Tree height/depth ---
def height(root: TreeNode | None) -> int:
    if not root:
        return 0
    return 1 + max(height(root.left), height(root.right))

# --- Diameter of binary tree ---
def diameter(root: TreeNode | None) -> int:
    res = [0]
    def dfs(node: TreeNode | None) -> int:
        if not node:
            return 0
        l, r = dfs(node.left), dfs(node.right)
        res[0] = max(res[0], l + r)
        return 1 + max(l, r)
    dfs(root)
    return res[0]

# --- Maximum path sum (any path, not just root-to-leaf) ---
def max_path_sum(root: TreeNode | None) -> int:
    res = [float("-inf")]
    def dfs(node: TreeNode | None) -> int:
        if not node:
            return 0
        l = max(dfs(node.left), 0)   # discard negative branches
        r = max(dfs(node.right), 0)
        res[0] = max(res[0], node.val + l + r)
        return node.val + max(l, r)
    dfs(root)
    return res[0]

# --- Lowest Common Ancestor (LCA) — BST ---
def lca_bst(root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
    cur = root
    while cur:
        if p.val < cur.val and q.val < cur.val:
            cur = cur.left
        elif p.val > cur.val and q.val > cur.val:
            cur = cur.right
        else:
            return cur
    return cur  # never None if p, q in tree

# --- LCA — general binary tree ---
def lca(root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None:
    if not root or root is p or root is q:
        return root
    left  = lca(root.left, p, q)
    right = lca(root.right, p, q)
    return root if left and right else left or right

# --- Validate BST ---
def is_valid_bst(root: TreeNode | None) -> bool:
    def validate(node, lo, hi):
        if not node:
            return True
        if not (lo < node.val < hi):
            return False
        return validate(node.left, lo, node.val) and validate(node.right, node.val, hi)
    return validate(root, float("-inf"), float("inf"))

# --- Serialize / Deserialize binary tree ---
def serialize(root: TreeNode | None) -> str:
    res = []
    def dfs(node):
        if not node:
            res.append("N"); return
        res.append(str(node.val))
        dfs(node.left); dfs(node.right)
    dfs(root)
    return ",".join(res)

def deserialize(data: str) -> TreeNode | None:
    vals = iter(data.split(","))
    def dfs():
        v = next(vals)
        if v == "N":
            return None
        node = TreeNode(int(v))
        node.left = dfs(); node.right = dfs()
        return node
    return dfs()

# --- Construct tree from preorder + inorder ---
def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:
    if not preorder:
        return None
    root = TreeNode(preorder[0])
    mid = inorder.index(preorder[0])
    root.left  = build_tree(preorder[1:mid+1], inorder[:mid])
    root.right = build_tree(preorder[mid+1:],  inorder[mid+1:])
    return root
    # O(n²) — for O(n): pre-build {val: index} map for inorder

9.4 Key CP Patterns

ProblemDFS return valueGlobal tracked
Heightint depth—
Diameterint depthmax l+r
Max path sumint max one-side gainmax node + l + r
Count nodesint count—
Is balancedint height or -1—
Any root-to-leaf path sumbool—