Tree Fundamentals

Back

Loading concept...

Binary Trees: Tree Fundamentals 🌳

Imagine a magical family tree where everyone has at most two children. That’s a Binary Tree!


The Big Picture

Think of a Binary Tree like an upside-down tree in a park. The roots are at the top (we call it the root node), and branches grow downward. Each branch can split into at most two smaller branches.

Why do we care? Binary Trees help computers organize and find data super fast—like finding a name in a phone book without checking every single page!


1. Binary Tree Fundamentals

What IS a Binary Tree?

Picture a family where every parent can have at most 2 children—a left child and a right child. That’s it!

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

Real-Life Example:

       📦 CEO (Root)
       /      \
    📦 CTO    📦 CFO
    /    \        \
 📦 Dev  📦 QA   📦 Accountant

Key Terms (Simple Definitions)

Term Meaning Example
Root The topmost node CEO in our company tree
Leaf A node with NO children Dev, QA, Accountant
Parent A node that has children below it CTO is parent of Dev
Child A node connected below another Dev is child of CTO
Edge The line connecting parent to child The “/” and "" lines

Building Your First Tree

# Create nodes
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

#     1
#    / \
#   2   3
#  / \
# 4   5

2. Tree Traversal Techniques

The Big Idea

Imagine you’re visiting every room in a house. Traversal means visiting every node in the tree—but in what ORDER?

There are 3 main ways to walk through a tree:

2.1 Inorder Traversal (Left → Root → Right)

Story: Visit left child first, then yourself, then right child.

def inorder(node):
    if not node:
        return
    inorder(node.left)   # Go left
    print(node.val)      # Visit me
    inorder(node.right)  # Go right

Example Tree:

    2
   / \
  1   3

Inorder Result: 1, 2, 3 ✨ (Sorted order for BST!)

2.2 Preorder Traversal (Root → Left → Right)

Story: Visit yourself FIRST, then explore children.

def preorder(node):
    if not node:
        return
    print(node.val)       # Visit me first!
    preorder(node.left)   # Then left
    preorder(node.right)  # Then right

Same Tree Result: 2, 1, 3

2.3 Postorder Traversal (Left → Right → Root)

Story: Save the best (yourself) for LAST!

def postorder(node):
    if not node:
        return
    postorder(node.left)
    postorder(node.right)
    print(node.val)       # Visit me last!

Same Tree Result: 1, 3, 2

2.4 Level Order (BFS)

Story: Visit floor by floor, like scanning a building.

from collections import deque

def level_order(root):
    if not root:
        return []
    queue = deque([root])
    result = []
    while queue:
        node = queue.popleft()
        result.append(node.val)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    return result

Result: 2, 1, 3 (top to bottom, left to right)


3. Tree Height and Depth

What’s the Difference?

Concept Definition Measured From
Depth Distance from ROOT to a node Top → Down
Height Distance from a node to deepest LEAF Bottom → Up

Think of it like a building:

  • Depth = Which floor am I on? (counting from roof)
  • Height = How many floors below me?
       1        ← Depth 0, Height 2
      / \
     2   3      ← Depth 1, Height 1
    /
   4            ← Depth 2, Height 0 (leaf!)

Calculate Height

def height(node):
    if not node:
        return -1  # Empty tree
    left_h = height(node.left)
    right_h = height(node.right)
    return 1 + max(left_h, right_h)

Example: For the tree above, height(root) = 2


4. Tree Diameter

The Big Question

Diameter = The longest path between ANY two nodes in the tree.

Key Insight: The longest path might NOT go through the root!

        1
       / \
      2   3
     / \
    4   5
   /     \
  6       7

Longest path: 6 → 4 → 2 → 5 → 7 = 4 edges

Calculate Diameter

def diameter(root):
    result = [0]  # Store max diameter

    def height(node):
        if not node:
            return 0
        left = height(node.left)
        right = height(node.right)
        # Update diameter: path through node
        result[0] = max(result[0], left + right)
        return 1 + max(left, right)

    height(root)
    return result[0]

Time: O(n) — we visit each node once!


5. Binary Tree Maximum Path Sum

The Challenge

Find the path with the maximum sum of node values. The path can start and end at ANY node!

Example:

      -10
      /  \
     9    20
         /  \
        15   7

Best Path: 15 → 20 → 7 = 42

The Strategy

At each node, decide:

  1. Should I extend the path upward?
  2. Is the path through me the best so far?
def max_path_sum(root):
    result = [float('-inf')]

    def helper(node):
        if not node:
            return 0
        # Only take positive contributions
        left = max(0, helper(node.left))
        right = max(0, helper(node.right))
        # Path through current node
        current_sum = node.val + left + right
        result[0] = max(result[0], current_sum)
        # Return max path extending upward
        return node.val + max(left, right)

    helper(root)
    return result[0]

Key Trick: Use max(0, ...) to ignore negative paths!


6. Lowest Common Ancestor (LCA)

What is LCA?

The Lowest Common Ancestor of two nodes is the deepest node that has BOTH nodes as descendants.

Think of it like: “What’s the first meeting point if two cousins trace back their family tree?”

        3
       / \
      5   1
     / \ / \
    6  2 0  8
  • LCA of 6 and 2? → 5 (parent of both)
  • LCA of 6 and 8? → 3 (root is the meeting point)
  • LCA of 5 and 1? → 3

Find LCA

def lowest_common_ancestor(root, p, q):
    if not root:
        return None
    if root == p or root == q:
        return root

    left = lowest_common_ancestor(
        root.left, p, q
    )
    right = lowest_common_ancestor(
        root.right, p, q
    )

    # If both sides found something
    if left and right:
        return root
    # Return whichever side found it
    return left if left else right

Logic: If p is in left subtree and q is in right subtree, the current node is the LCA!


7. Path Sum Problems

Problem 7.1: Does a Path Exist with Target Sum?

Question: Is there a root-to-leaf path where node values add up to target?

        5
       / \
      4   8
     /   / \
    11  13  4
   /  \      \
  7    2      1

Target = 22 → Path: 5 → 4 → 11 → 2

def has_path_sum(root, target):
    if not root:
        return False
    # Check if leaf with exact sum
    if not root.left and not root.right:
        return root.val == target
    # Try both children with reduced target
    return (
        has_path_sum(root.left, target - root.val)
        or
        has_path_sum(root.right, target - root.val)
    )

Problem 7.2: Find ALL Paths with Target Sum

def path_sum(root, target):
    result = []

    def dfs(node, remaining, path):
        if not node:
            return
        path.append(node.val)
        # If leaf and sum matches
        if not node.left and not node.right:
            if remaining == node.val:
                result.append(list(path))
        else:
            dfs(node.left, remaining - node.val, path)
            dfs(node.right, remaining - node.val, path)
        path.pop()  # Backtrack!

    dfs(root, target, [])
    return result

Problem 7.3: Path Sum III (Any Starting Point)

Count paths that sum to target—they can start from ANY node!

def path_sum_iii(root, target):
    def count_paths(node, curr_sum):
        if not node:
            return 0
        curr_sum += node.val
        # Paths ending here with target sum
        count = prefix_sums.get(
            curr_sum - target, 0
        )
        prefix_sums[curr_sum] = (
            prefix_sums.get(curr_sum, 0) + 1
        )
        # Explore children
        count += count_paths(node.left, curr_sum)
        count += count_paths(node.right, curr_sum)
        # Backtrack
        prefix_sums[curr_sum] -= 1
        return count

    prefix_sums = {0: 1}
    return count_paths(root, 0)

Magic Trick: Use a prefix sum hashmap to track all possible starting points!


Summary Diagram

graph TD A["Binary Tree Fundamentals"] --> B["Traversals"] A --> C["Height & Depth"] A --> D["Diameter"] A --> E["Max Path Sum"] A --> F["LCA"] A --> G["Path Sum Problems"] B --> B1["Inorder: L-Root-R"] B --> B2["Preorder: Root-L-R"] B --> B3["Postorder: L-R-Root"] B --> B4["Level Order: BFS"] G --> G1["Has Path Sum?"] G --> G2["All Paths"] G --> G3["Any Start Point"]

Quick Reference Table

Problem Key Technique Time Space
Traversals Recursion/Queue O(n) O(h)
Height Recursion O(n) O(h)
Diameter Track max during height O(n) O(h)
Max Path Sum DFS + track global max O(n) O(h)
LCA Recursive search both sides O(n) O(h)
Path Sum I DFS with target reduction O(n) O(h)
Path Sum III Prefix sum + hashmap O(n) O(n)

h = height of tree (log n for balanced, n for skewed)


You’ve Got This! 🎉

Binary trees might seem complex at first, but they’re just upside-down trees with simple rules:

  • Each node has at most 2 children
  • We can visit nodes in different orders (traversals)
  • Most problems use recursion to break down the tree

Practice these patterns, and soon you’ll see trees everywhere in computer science—databases, file systems, decision making, and more!

Next Step: Try implementing each algorithm yourself. Start with the simpler ones (traversals, height) and work your way up to path sum problems. You’ve got this! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.