← All Guides

Advanced Trees

6 drills · 6 pattern exercises · 12 micro drills

Patterns Overview

Patterns used in this topic, with difficulty breakdown.

Recursive Descent 2 drills 1 Easy 1 Med
Recursive Max 2 drills 2 Easy
Preorder 1 drill 1 Med
Queue by Level 1 drill 1 Med

Foundation Drill

90 Diameter of binary tree Easy Recursive Max

Find the longest path between any two nodes in a binary tree, measured in edges.

Start here in Foundations →

Coding Drills

All 6 drills grouped by pattern. Each includes prompt, solution, and complexity.

Recursive Descent (2)

89 Lowest common ancestor Medium

In plain English: Find the deepest node in a binary tree that is an ancestor of both given nodes.

Recurse left and right. If p and q are in different subtrees, the current node is the LCA. If both are on one side, the LCA is in that subtree.

Prompt

Given a binary tree and two nodes p and q, find their lowest common ancestor (LCA).

Solution

def lowest_common_ancestor(root, p, q):
    if root is None or root == p or root == 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 if left else right

# Test: tree = [3,5,1,6,2,0,8], LCA(5,1) = 3, LCA(5,4) = 5
O(n) time · O(h) space
92 Path sum Easy

In plain English: Check if any path from the root to a leaf adds up to a given target number.

Subtract the current node's value from the target as you descend. At a leaf, check if the remaining target is zero.

Prompt

Given a binary tree and a target sum, determine if there is a root-to-leaf path that sums to the target.

Solution

def has_path_sum(root, target):
    if not root:
        return False
    target -= root.val
    if not root.left and not root.right:
        return target == 0  # leaf: check if sum matches
    return has_path_sum(root.left, target) or has_path_sum(root.right, target)

# Test: tree [5,4,8,11,null,13,4,7,2,null,null,null,1], target=22 => True
# Path: 5->4->11->2
O(n) time · O(h) space

Recursive Max (2)

90 Diameter of binary tree Easy

In plain English: Find the longest path between any two nodes in a binary tree, measured in edges.

At each node, the longest path through it is left_height + right_height. Track the global maximum while computing heights bottom-up.

Prompt

Find the diameter of a binary tree — the length of the longest path between any two nodes (in edges).

Solution

def diameter_of_binary_tree(root):
    diameter = [0]
    def height(node):
        if not node:
            return 0
        lh = height(node.left)
        rh = height(node.right)
        diameter[0] = max(diameter[0], lh + rh)  # path through this node
        return 1 + max(lh, rh)
    height(root)
    return diameter[0]

# Test: tree [1,2,3,4,5] => diameter = 3 (path 4->2->1->3)
O(n) time · O(h) space
94 Symmetric tree Easy

In plain English: Check whether the left and right halves of a binary tree are mirror images of each other.

Two subtrees are mirrors if their roots match and each tree's left subtree mirrors the other's right subtree — recurse with crossed pairs.

Prompt

Check if a binary tree is symmetric (a mirror image of itself).

Solution

def is_symmetric(root):
    def is_mirror(t1, t2):
        if not t1 and not t2:
            return True
        if not t1 or not t2:
            return False
        return (t1.val == t2.val and
                is_mirror(t1.left, t2.right) and  # cross-compare
                is_mirror(t1.right, t2.left))
    return is_mirror(root, root) if root else True

# Test: tree [1,2,2,3,4,4,3] => True
# Test: tree [1,2,2,null,3,null,3] => False
O(n) time · O(h) space

Preorder (1)

91 Serialize and deserialize BST Medium

In plain English: Convert a binary tree to a string so it can be saved, then rebuild the tree from that string.

Preorder traversal with null markers uniquely identifies a tree. Serialize: visit, left, right. Deserialize: read values and recurse, using an iterator.

Prompt

Serialize a binary tree to a string and deserialize it back to the original tree structure.

Solution

class Codec:
    def serialize(self, root):
        vals = []
        def preorder(node):
            if not node:
                vals.append('#')  # null marker so structure is unambiguous
                return
            vals.append(str(node.val))
            preorder(node.left)
            preorder(node.right)
        preorder(root)
        return ','.join(vals)

    def deserialize(self, data):
        # iterator lets recursive calls consume tokens in order without index bookkeeping
        vals = iter(data.split(','))
        def build():
            v = next(vals)
            if v == '#':
                return None
            node = TreeNode(int(v))
            node.left = build()
            node.right = build()
            return node
        return build()

# Test: tree [1,2,3] => "1,2,#,#,3,#,#" => back to tree
O(n) time · O(n) space

Queue by Level (1)

93 Binary tree right side view Medium

In plain English: Look at a binary tree from the right side and list the nodes you can see at each level.

Level-order traversal where you record the last node at each level — that is the rightmost node visible from the right side.

Prompt

Given a binary tree, return the values of the nodes you can see from the right side, ordered from top to bottom.

Solution

from collections import deque

def right_side_view(root):
    if not root:
        return []
    result = []
    q = deque([root])
    while q:
        level_size = len(q)
        for i in range(level_size):
            node = q.popleft()
            if i == level_size - 1:
                result.append(node.val)  # last node in level = rightmost
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
    return result

# Test: tree [1,2,3,null,5,null,4] => [1, 3, 4]
O(n) time · O(n) space

Pattern Recognition

6 exercises: spot the signals, pick the method, avoid the trap.

89 Lowest common ancestor Medium

Find the LCA of two nodes in a binary tree.

Signals

Tree Recursion

Recurse left and right. If both return non-null, current node is LCA. If one is null, return the other. O(n).

Trap

For BSTs, you can use the BST property to go left/right more efficiently. But for general trees, full recursion is needed.

90 Diameter of binary tree Easy

Find the diameter (longest path between any two nodes).

Signals

Tree Recursion

Height recursion, update global max = left_h + right_h at each node. O(n).

Trap

The diameter doesn't necessarily pass through the root! Must check at every node.

91 Serialize and deserialize BST Medium

Serialize a binary tree to a string and reconstruct it.

Signals

Tree Recursion

Serialize: preorder with '#' for nulls. Deserialize: read values in order, recurse. O(n).

Queue / BFS

Level-order serialization with null markers also works. Same O(n).

Trap

Need null markers to distinguish tree shapes. Without them, different trees can have same traversal.

92 Path sum Easy

Is there a root-to-leaf path that sums to a target?

Signals

Tree Recursion

Recurse with target - node.val. At leaf: return target == node.val. O(n).

Trap

Must check at leaf nodes, not at null nodes. A leaf has no children.

93 Binary tree right side view Medium

Return values visible from the right side (rightmost node per level).

Signals

Queue / BFS

Level-order BFS. For each level, the last node is the rightmost. O(n).

DFS / Recursion

DFS visiting right before left. First node at each new depth is the answer. O(n).

Trap

It's not just the rightmost branch — nodes from the left can appear if right subtree is shorter.

94 Symmetric tree Easy

Check if a binary tree is its own mirror image.

Signals

Tree Recursion

is_mirror(left, right): both null → True. One null → False. Values match AND subtrees mirror. O(n).

Queue / BFS

BFS with pairs: enqueue (left.left, right.right) and (left.right, right.left). Same O(n).

Trap

Don't just check if inorder traversal is a palindrome — that's necessary but not sufficient.

Related Micro Drills

12 quick recall drills to reinforce this topic.

212 Lowest common ancestor (BST) Tree Traversal 10s

BST property: if both targets < root go left, both > root go right, otherwise root is LCA. O(h) time.

def lowestCommonAncestor(root, p, q):
    while root:
        if p.val &lt; root.val and q.val &lt; root.val:
            root = root.left
        elif p.val &gt; root.val and q.val &gt; root.val:
            root = root.right
        else:
            return root
213 Lowest common ancestor (binary tree) Tree Traversal 10s

If both sides return non-null, current node is LCA. Otherwise, LCA is on the non-null side. O(n) time.

def lowestCommonAncestor(root, p, q):
    if not root or root == p or root == q:
        return root
    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)
    if left and right: return root  # split point = LCA
    return left or right
205 Invert binary tree Tree Traversal 5s

Swap left and right children recursively. The classic 'Homebrew' interview question. Must be instant.

def invertTree(root):
    if not root: return None
    root.left, root.right = invertTree(root.right), invertTree(root.left)
    return root
139 Diameter of binary tree Tree Traversal 10s

Diameter is the max left_height + right_height at any node. Track global max during DFS.

dia = 0
def h(n):
    global dia
    if not n: return 0
    l, r = h(n.left), h(n.right)
    dia = max(dia, l + r)
    return 1 + max(l, r)
60 Tree height Tree Traversal 10s

Height calculation is the basis for balanced-tree checks and diameter computation.

def height(node):
    if not node: return 0
    return 1 + max(height(node.left), height(node.right))
215 Deserialize binary tree Tree Traversal 10s

Consume preorder tokens with an iterator. '#' = null leaf. Recursion naturally reconstructs the tree structure.

def deserialize(data):
    vals = iter(data.split(','))
    def dfs():
        val = next(vals)
        if val == '#': return None
        node = TreeNode(int(val))
        node.left = dfs()
        node.right = dfs()
        return node
    return dfs()
214 Serialize binary tree (preorder) Tree Traversal 10s

Preorder with null markers (#). Each node becomes: val, left_subtree, right_subtree. Null markers preserve structure.

def serialize(root):
    res = []
    def dfs(node):
        if not node:
            res.append('#')
            return
        res.append(str(node.val))
        dfs(node.left)
        dfs(node.right)
    dfs(root)
    return ','.join(res)
209 Path sum (root to leaf) Tree Traversal 10s

Subtract node value as you go down. At a leaf, check if remaining equals node value. Top-down DFS.

def hasPathSum(root, targetSum):
    if not root: return False
    if not root.left and not root.right:
        return root.val == targetSum
    return (hasPathSum(root.left, targetSum - root.val) or
            hasPathSum(root.right, targetSum - root.val))
210 Path sum II (collect all paths) Tree Traversal 15s

Backtracking on tree: append node, recurse, pop. Collect path copy at valid leaves. Classic tree + backtrack combo.

def pathSum(root, targetSum):
    res = []
    def dfs(node, remaining, path):
        if not node: return
        path.append(node.val)
        if not node.left and not node.right and remaining == node.val:
            res.append(path[:])  # copy!
        dfs(node.left, remaining - node.val, path)
        dfs(node.right, remaining - node.val, path)
        path.pop()  # backtrack
    dfs(root, targetSum, [])
    return res
217 Right side view (BFS) Tree Traversal 15s

BFS level-by-level. Take the last node of each level. Standard level-order with level_size tracking.

from collections import deque

def rightSideView(root):
    if not root: return []
    res = []
    q = deque([root])
    while q:
        level_size = len(q)
        for i in range(level_size):
            node = q.popleft()
            if i == level_size - 1:
                res.append(node.val)  # rightmost in level
            if node.left: q.append(node.left)
            if node.right: q.append(node.right)
    return res
207 Symmetric tree check Tree Traversal 10s

Mirror check: compare left.left with right.right AND left.right with right.left. Like 'same tree' but crossed.

def isSymmetric(root):
    def mirror(a, b):
        if not a and not b: return True
        if not a or not b: return False
        return (a.val == b.val and
                mirror(a.left, b.right) and
                mirror(a.right, b.left))
    return mirror(root.left, root.right)
206 Same tree check Tree Traversal 10s

Both null → True. One null → False. Values equal AND both subtrees same → True. Base case for subtree check.

def isSameTree(p, q):
    if not p and not q: return True
    if not p or not q: return False
    return (p.val == q.val and
            isSameTree(p.left, q.left) and
            isSameTree(p.right, q.right))