DEV Community

M.T.Ramkrushna
M.T.Ramkrushna

Posted on

Topic 9: Trees β€” Binary Trees 🌳

Trees are one of the most important DSA topics for coding interviews and exams.

A tree is a hierarchical data structure made of nodes connected by edges.

Example:

        1
       / \
      2   3
     / \   \
    4   5   6
Enter fullscreen mode Exit fullscreen mode
  • 1 is the root

  • 2 and 3 are children of 1

  • 4, 5, and 6 are leaf nodes

  • The height of this tree is 2 edges

1. Binary Tree

A binary tree is a tree where each node has at most two children:

  • left

  • right

Python representation

Python

Run

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

Creating a tree:

Python

Run

root = TreeNode(1)

root.left = TreeNode(2)
root.right = TreeNode(3)

root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
Enter fullscreen mode Exit fullscreen mode

2. Tree Traversals

Traversal means visiting every node in a particular order.

The three major DFS traversals are:

  1. Preorder

  2. Inorder

  3. Postorder

There is also:

  1. Level-order traversal, using BFS

3. Preorder Traversal

Order:

Root β†’ Left β†’ Right
Enter fullscreen mode Exit fullscreen mode

For this tree:

        1
       / \
      2   3
     / \
    4   5
Enter fullscreen mode Exit fullscreen mode

Preorder result:

[1, 2, 4, 5, 3]
Enter fullscreen mode Exit fullscreen mode

Recursive solution

Python

Run

def preorder(root):
    result = []

    def dfs(node):
        if node is None:
            return

        result.append(node.val)
        dfs(node.left)
        dfs(node.right)

    dfs(root)
    return result
Enter fullscreen mode Exit fullscreen mode

Interview pattern

Python

Run

def dfs(node):
    if node is None:
        return

    # Process current node
    dfs(node.left)
    dfs(node.right)
Enter fullscreen mode Exit fullscreen mode

Preorder is useful when you need to process the parent before its children.

4. Inorder Traversal

Order:

Left β†’ Root β†’ Right
Enter fullscreen mode Exit fullscreen mode

For the same tree:

        1
       / \
      2   3
     / \
    4   5
Enter fullscreen mode Exit fullscreen mode

Inorder result:

[4, 2, 5, 1, 3]
Enter fullscreen mode Exit fullscreen mode

Recursive solution

Python

Run

def inorder(root):
    result = []

    def dfs(node):
        if node is None:
            return

        dfs(node.left)
        result.append(node.val)
        dfs(node.right)

    dfs(root)
    return result
Enter fullscreen mode Exit fullscreen mode

Important interview fact

For a Binary Search Tree, inorder traversal gives values in sorted order.

Example BST:

        4
       / \
      2   6
     / \ / \
    1  3 5  7
Enter fullscreen mode Exit fullscreen mode

Inorder:

[1, 2, 3, 4, 5, 6, 7]
Enter fullscreen mode Exit fullscreen mode

5. Postorder Traversal

Order:

Left β†’ Right β†’ Root
Enter fullscreen mode Exit fullscreen mode

Result for the tree:

[4, 5, 2, 3, 1]
Enter fullscreen mode Exit fullscreen mode

Recursive solution

Python

Run

def postorder(root):
    result = []

    def dfs(node):
        if node is None:
            return

        dfs(node.left)
        dfs(node.right)
        result.append(node.val)

    dfs(root)
    return result
Enter fullscreen mode Exit fullscreen mode

Postorder is useful when a node depends on the results of its children.

Examples:

  • Deleting a tree

  • Calculating subtree sizes

  • Calculating tree height

  • Dynamic programming on trees

6. Level-Order Traversal β€” BFS

Level-order visits nodes level by level.

Example:

        1
       / \
      2   3
     / \   \
    4   5   6
Enter fullscreen mode Exit fullscreen mode

Result:

[[1], [2, 3], [4, 5, 6]]
Enter fullscreen mode Exit fullscreen mode

Use collections.deque as a queue.

Python

Run

from collections import deque

def level_order(root):
    if root is None:
        return []

    result = []
    queue = deque([root])

    while queue:
        level = []

        for _ in range(len(queue)):
            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
Enter fullscreen mode Exit fullscreen mode

Complexity

  • Time: O(n)

  • Space: O(n) in the worst case

7. Maximum Depth of a Binary Tree

The depth is the number of nodes on the longest root-to-leaf path.

Example:

        1
       /
      2
     /
    3
Enter fullscreen mode Exit fullscreen mode

Maximum depth:

3
Enter fullscreen mode Exit fullscreen mode

Recursive solution

Python

Run

def max_depth(root):
    if root is None:
        return 0

    left_depth = max_depth(root.left)
    right_depth = max_depth(root.right)

    return 1 + max(left_depth, right_depth)
Enter fullscreen mode Exit fullscreen mode

Core idea

The depth of a node is:

1 + max(depth of left subtree, depth of right subtree)
Enter fullscreen mode Exit fullscreen mode

This is a very common tree recursion pattern.

8. Same Tree

Given two binary trees, determine whether they are identical.

Two trees are identical if:

  • Their values are equal

  • Their left subtrees are identical

  • Their right subtrees are identical

Python

Run

def is_same_tree(p, q):
    if p is None and q is None:
        return True

    if p is None or q is None:
        return False

    if p.val != q.val:
        return False

    return (
        is_same_tree(p.left, q.left)
        and is_same_tree(p.right, q.right)
    )
Enter fullscreen mode Exit fullscreen mode

Complexity

  • Time: O(n)

  • Space: O(h) recursion stack

  • h is the height of the tree

9. Invert a Binary Tree

Invert means swapping the left and right children of every node.

Before:

        1
       / \
      2   3
     / \
    4   5
Enter fullscreen mode Exit fullscreen mode

After:

        1
       / \
      3   2
         / \
        5   4
Enter fullscreen mode Exit fullscreen mode

Recursive solution

Python

Run

def invert_tree(root):
    if root is None:
        return None

    root.left, root.right = root.right, root.left

    invert_tree(root.left)
    invert_tree(root.right)

    return root
Enter fullscreen mode Exit fullscreen mode

Important pattern

When the problem asks you to modify every node, consider:

Python

Run

process current node
recurse left
recurse right
Enter fullscreen mode Exit fullscreen mode

10. Count Number of Nodes

Python

Run

def count_nodes(root):
    if root is None:
        return 0

    return (
        1
        + count_nodes(root.left)
        + count_nodes(root.right)
    )
Enter fullscreen mode Exit fullscreen mode

The formula is:

size(tree) = 1 + size(left subtree) + size(right subtree)
Enter fullscreen mode Exit fullscreen mode

11. Sum of All Nodes

Python

Run

def tree_sum(root):
    if root is None:
        return 0

    return (
        root.val
        + tree_sum(root.left)
        + tree_sum(root.right)
    )
Enter fullscreen mode Exit fullscreen mode

This is another example of postorder-style tree recursion.

12. Path Sum

Determine whether there is a root-to-leaf path whose values add up to targetSum.

Example:

        5
       / \
      4   8
     /   / \
    11  13  4
   /  \      \
  7    2      1
Enter fullscreen mode Exit fullscreen mode

Target:

22
Enter fullscreen mode Exit fullscreen mode

Path:

5 β†’ 4 β†’ 11 β†’ 2 = 22
Enter fullscreen mode Exit fullscreen mode

Solution

Python

Run

def has_path_sum(root, target_sum):
    if root is None:
        return False

    if root.left is None and root.right is None:
        return root.val == target_sum

    remaining = target_sum - root.val

    return (
        has_path_sum(root.left, remaining)
        or has_path_sum(root.right, remaining)
    )
Enter fullscreen mode Exit fullscreen mode

Important detail

A path must usually end at a leaf, not just any node.

13. Diameter of a Binary Tree

The diameter is the longest path between any two nodes.

It may or may not pass through the root.

Example:

        1
       / \
      2   3
     / \
    4   5
Enter fullscreen mode Exit fullscreen mode

Longest path:

4 β†’ 2 β†’ 1 β†’ 3
Enter fullscreen mode Exit fullscreen mode

Diameter:

3 edges
Enter fullscreen mode Exit fullscreen mode

Solution

Python

Run

def diameter_of_binary_tree(root):
    diameter = 0

    def height(node):
        nonlocal diameter

        if node is None:
            return 0

        left_height = height(node.left)
        right_height = height(node.right)

        diameter = max(
            diameter,
            left_height + right_height
        )

        return 1 + max(left_height, right_height)

    height(root)
    return diameter
Enter fullscreen mode Exit fullscreen mode

Why does this work?

At each node:

longest path through node
= left height + right height
Enter fullscreen mode Exit fullscreen mode

We calculate the height while updating the maximum diameter.

Complexity

  • Time: O(n)

  • Space: O(h)

14. Balanced Binary Tree

A tree is height-balanced if, at every node:

abs(left height - right height) <= 1
Enter fullscreen mode Exit fullscreen mode

Efficient solution

Python

Run

def is_balanced(root):
    def height(node):
        if node is None:
            return 0

        left = height(node.left)
        if left == -1:
            return -1

        right = height(node.right)
        if right == -1:
            return -1

        if abs(left - right) > 1:
            return -1

        return 1 + max(left, right)

    return height(root) != -1
Enter fullscreen mode Exit fullscreen mode

Why return -1?

-1 acts as a signal that an unbalanced subtree has been found.

This avoids repeatedly calculating heights.

15. Iterative Inorder Traversal

Recursive traversal is easier, but interviewers may ask for an iterative version.

Python

Run

def inorder_iterative(root):
    result = []
    stack = []
    current = root

    while current or stack:
        while current:
            stack.append(current)
            current = current.left

        current = stack.pop()
        result.append(current.val)

        current = current.right

    return result
Enter fullscreen mode Exit fullscreen mode

Main idea

  1. Keep moving left.

  2. Push nodes into the stack.

  3. Process the most recent node.

  4. Move to its right subtree.

16. Tree Problem Recognition

Problem wording Likely technique
Visit root, left, right Preorder
Sorted values in BST Inorder
Children before parent Postorder
Level by level BFS with deque
Maximum depth Recursive height
Longest path Height + global answer
Compare two trees DFS recursion
Root-to-leaf target DFS with remaining sum
Shortest path in unweighted tree BFS
Modify every node DFS
Need subtree result Postorder

17. Binary Tree vs Binary Search Tree

A binary tree only restricts the number of children.

A Binary Search Tree, or BST, also follows:

all values in left subtree < node.val
all values in right subtree > node.val
Enter fullscreen mode Exit fullscreen mode

Example:

        8
       / \
      3   10
     / \    \
    1   6    14
Enter fullscreen mode Exit fullscreen mode

Searching in a balanced BST:

  • Average: O(log n)

  • Worst case: O(n)

A completely skewed BST can behave like a linked list:

1
 \
  2
   \
    3
     \
      4
Enter fullscreen mode Exit fullscreen mode

18. Common Interview Mistakes

Mistake 1: Forgetting the base case

Always handle:

Python

Run

if root is None:
    ...
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Confusing depth and height

Depending on the problem:

  • Height may count edges

  • Height may count nodes

  • Read the definition carefully

Mistake 3: Using global variables incorrectly

If using a nested function:

Python

Run

nonlocal answer
Enter fullscreen mode Exit fullscreen mode

is needed to modify an outer local variable.

Mistake 4: Forgetting leaf conditions

For root-to-leaf problems:

Python

Run

if node.left is None and node.right is None:
Enter fullscreen mode Exit fullscreen mode

Mistake 5: Using list as a queue inefficiently

Avoid:

Python

Run

queue.pop(0)
Enter fullscreen mode Exit fullscreen mode

Use:

Python

Run

from collections import deque
queue.popleft()
Enter fullscreen mode Exit fullscreen mode

Must-Know Tree Templates

DFS template

Python

Run

def dfs(node):
    if node is None:
        return

    dfs(node.left)
    dfs(node.right)
Enter fullscreen mode Exit fullscreen mode

Return information from children

Python

Run

def dfs(node):
    if node is None:
        return 0

    left = dfs(node.left)
    right = dfs(node.right)

    return 1 + max(left, right)
Enter fullscreen mode Exit fullscreen mode

BFS template

Python

Run

from collections import deque

queue = deque([root])

while queue:
    node = queue.popleft()

    if node.left:
        queue.append(node.left)

    if node.right:
        queue.append(node.right)
Enter fullscreen mode Exit fullscreen mode

Practice Questions

Try solving these in order:

Easy

  1. Maximum Depth of Binary Tree

  2. Same Tree

  3. Invert Binary Tree

  4. Count Good Nodes in Binary Tree

  5. Binary Tree Preorder Traversal

  6. Binary Tree Inorder Traversal

  7. Binary Tree Postorder Traversal

Medium

  1. Binary Tree Level Order Traversal

  2. Path Sum

  3. Diameter of Binary Tree

  4. Balanced Binary Tree

  5. Lowest Common Ancestor of a Binary Tree

  6. Right Side View of Binary Tree

  7. Validate Binary Search Tree

Important exam question

Implement all four traversals for this tree:

        1
       / \
      2   3
     / \   \
    4   5   6
Enter fullscreen mode Exit fullscreen mode

Expected answers:

Preorder:   [1, 2, 4, 5, 3, 6]
Inorder:    [4, 2, 5, 1, 3, 6]
Postorder:  [4, 5, 2, 6, 3, 1]
Level order: [[1], [2, 3], [4, 5, 6]]
Enter fullscreen mode Exit fullscreen mode

Next topic: Binary Search Trees (BST), including search, insertion, deletion, validation, and lowest common ancestor.

Top comments (0)