AI SWE Prep
0/23in Data Structures & Algorithms
Data Structures & Algorithms

Trees & Binary Search Trees

Traversals, the recursive shape of tree problems, and why an in-order walk of a BST comes out sorted.

~18 minLesson 23 of 60
Your progressNot started

A tree is a graph with hierarchy: one root, child links, and no cycles. That shape makes many problems feel almost too simple once you trust recursion. Solve the left subtree, solve the right subtree, then combine the two answers at the current node.

Binary search trees add ordering. Every value in the left subtree is smaller than the node, and every value in the right subtree is larger. That one invariant turns search, insertion, and validation into guided walks instead of blind traversals.

The mental model

A binary tree node stores a value and up to two child references:

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

Most tree code has the same recursive shape:

def solve(node):
    if node is None:
        return base_answer

    left = solve(node.left)
    right = solve(node.right)
    return combine(node.val, left, right)

Traversal is the order in which you visit nodes:

  • Preorder — node, left, right. Useful when the parent should be processed before children, like copying or serializing a tree.
  • Inorder — left, node, right. In a BST, this produces values in sorted order.
  • Postorder — left, right, node. Useful when the parent depends on child answers, like deleting or computing subtree sizes.
  • Level-order — breadth-first by depth, using a queue.
from collections import deque


def inorder(node):
    if not node:
        return []
    return inorder(node.left) + [node.val] + inorder(node.right)


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

For BSTs, the invariant is global, not just parent-child. Every node in the entire left subtree must be below the current value; every node in the entire right subtree must be above it. That is why bounds are safer than only comparing a node with its immediate children.

Binary tree traversal · Pre-order (root → L → R)Step 1 / 8
1234567
output:press play →
Pre-order (root → L → R) — watch the order nodes are emitted.

Pattern recognition

Variations

Worked problems

The first two problems are traversal mechanics. The third is the BST invariant in its most interview-relevant form.

LeetCode 104Easy
  • Tree
  • DFS

Maximum Depth of Binary Tree

Given the root of a binary tree, return the number of nodes on the longest path from the root down to any leaf. An empty tree has depth 0.

Approach. Define depth(node) as the depth of the subtree rooted at node. The empty subtree contributes 0. A real node contributes 1 plus the deeper of its two children. This is the canonical tree recursion shape.

Show solution
def max_depth(root):
    if root is None:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))

Complexity. O(n) time because every node is visited once. Space is O(h) for the recursion stack, where h is the tree height.

LeetCode 102Medium
  • Tree
  • BFS

Binary Tree Level Order Traversal

Given a binary tree, return its values grouped by depth from top to bottom. Each inner list should contain the nodes from one level, left to right.

Approach. BFS naturally processes nodes by distance from the root. At the start of each loop, the queue contains exactly one level. Capture its current length, pop that many nodes, and enqueue their children for the next round.

Show solution
from collections import deque


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

    ans = []
    q = deque([root])
    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)
        ans.append(level)
    return ans

Complexity. O(n) time and O(w) space, where w is the maximum width of the tree. In the worst case, w can be O(n).

LeetCode 98Medium
  • BST
  • DFS
  • Bounds

Validate Binary Search Tree

Given a binary tree, decide whether it satisfies the binary-search-tree rule: every node in a left subtree is smaller than its ancestor, and every node in a right subtree is larger.

Approach. A local check against children is not enough. A node deep in the right subtree must still be greater than the original root. Carry an open lower and upper bound as you recurse. Going left tightens the upper bound; going right tightens the lower bound.

Show solution
def is_valid_bst(root):
    def valid(node, low, high):
        if node is None:
            return True
        if not (low < node.val < high):
            return False
        return valid(node.left, low, node.val) and valid(node.right, node.val, high)

    return valid(root, float("-inf"), float("inf"))

Complexity. O(n) time and O(h) space for the recursion stack. The bounds make the global BST invariant explicit at every node.

The classic pitfall

Your progressNot started