DSA sheet · Trees · DFS pattern

Maximum Depth & Minimum Depth of a Binary Tree

Two very common warm-up problems, taught together because they look almost the same. The teacher solves maximum depth with BFS (count the levels) and DFS (ask each child for its height). Then she tries the obvious shortcut for minimum depth, changing max to min, and shows on screen that it gives a wrong answer. Fixing that mistake is the most important lesson here: an empty child is not a leaf. Finally she shows BFS makes minimum depth easy: stop at the first leaf you meet.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

given by LeetCode, don't write this in the solution
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Words we need

LeetCode example 1 → 3 levels
      3
     / \
    9   20
       /  \
      15   7
LeetCode example 2 → 2 levels
    1
     \
      2

In example 2, [1, null, 2] means 1 has no left child and has 2 as its right child. There are 2 levels, so the maximum depth is 2.

Part A · Maximum depth with BFS

LeetCode 104 · Maximum Depth of Binary Tree

1The question in simple words

Given the root, return the number of nodes on the longest path from the root down to a leaf. This is the teacher's own tree for this problem:

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

The longest path is 1 → 3 → 5 → 6, which has 4 nodes. Answer: 4.

2What the constraints tell us

3Intuition

Maximum depth = number of levels. BFS level order already walks the tree one level at a time: each round of the outer while loop handles exactly one level. So we just count the rounds.

4Building the logic: edit level order

Doubt: why depth += 1 after the for loop and not inside it?
→ Inside the for loop, it would add 1 for every node, and we'd be counting nodes (6), not levels (4). One round of the outer loop = one level, so the counter goes outside the inner loop.

5Approach steps

  1. If the root is None, return 0.
  2. Put the root in the queue; depth = 0.
  3. While the queue isn't empty: size = len(queue); pop size nodes and push their children.
  4. After each level, depth += 1.
  5. Return depth.

6Code (Python)

Maximum depth with BFS: count the levels
from collections import deque

class Solution:
    def maxDepth(self, root):
        if root is None:                    # 0 nodes allowed
            return 0
        queue = deque([root])
        depth = 0
        while queue:
            size = len(queue)
            for i in range(size):           # pop one whole level
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            depth += 1                      # one more level finished
        return depth

7Code line by line

linewhat it means
if root is None: return 0An empty tree has no levels.
depth = 0No levels counted yet.
size = len(queue)The queue holds exactly one level right now.
for i in range(size): ...Pop that level and push the next level. We don't use the values.
depth += 1Count the level we just finished.

8Dry run on the teacher's tree

round 11pop 1 → push 2, 3 → depth 1
round 223pop 2 (no children), pop 3 → push 4, 5 → depth 2
round 345pop 4 (no children), pop 5 → push 6 → depth 3
round 46pop 6 → nothing to push → depth 4
endqueue empty → the inner loop ran in 4 rounds → return 4

9Complexity & remember

               1
          /         \
        2             3
      /   \         /   \
     4     5       6     7
    / \   / \     / \   / \
   8   9 10  11  12 13 14  15     ← 8 of 15 nodes in the queue at once
Remember max depth with BFSMax depth = number of levels. Level order without storing; depth += 1 after each level.

Part B · Maximum depth with DFS

1The question, and 2 the constraints

Same question and constraints as Part A. This is recursion, so the base case is mandatory.

3Intuition: ask your children

Stand at the root. From there you can't see how deep the tree goes. But you can ask your left child "how tall is your side?" and your right child "how tall is yours?". In the teacher's tree, the left side (just 2) answers 1, and the right side (3, 4, 5, 6) answers 3.

Your own height is then the taller of the two answers, plus 1 for yourself: max(1, 3) + 1 = 4. Every node does the same thing, asking its own children. That's a recursive function.

height(node) = max(height(left), height(right)) + 1

4Building the logic

The base case: what does an empty spot return?

The teacher's habit: whenever you write a DFS function, start with "what if the node is None?". Since our function returns a height, the answer must be an integer, not a boolean. An empty spot adds no nodes to any path → return 0.

The recursive part

The calls return integers, so store them: left = self.maxDepth(root.left), right = self.maxDepth(root.right).

Max or sum? The teacher asks this explicitly

Look at node 2: left gives 0, right gives 0. What should 2 report to its parent? A node can only hand back one path going down from it. Node 3 has two paths below it (through 4 and through 5), but it can only return one of them, so it returns the longer. Adding them would count two different paths as if they were one. So it's max, not sum, plus 1 for the node itself.

Doubt: when would we add left and right?
→ When the question asks about a path that goes through a node from one side to the other, like the diameter problem later in the sheet. For the depth from the root down, it's always one side, so max.

5Approach steps

  1. If the node is None, return 0.
  2. Get the left height by recursion.
  3. Get the right height by recursion.
  4. Return max(left, right) + 1.

6Code (Python)

Maximum depth with DFS: max of the children + 1
class Solution:
    def maxDepth(self, root):
        if root is None:                    # empty spot adds nothing
            return 0
        left = self.maxDepth(root.left)     # height of the left side
        right = self.maxDepth(root.right)   # height of the right side
        return max(left, right) + 1         # taller side + me

7Code line by line

linewhat it means
if root is None: return 0Base case: stops the recursion, and makes an empty tree give 0.
left = self.maxDepth(root.left)Python pauses here until the whole left side has reported its height.
right = self.maxDepth(root.right)Then the right side.
return max(left, right) + 1The longer side, plus this node.

The main function is the DFS itself here. With a separate helper, the main function would just return dfs(root).

8Dry run with the call stack

  1. f(1) → go left: f(2) → go left: f(None) → 0, leaves the stack. f(2) right: f(None) → 0.
  2. f(2) has left 0, right 0 → max(0, 0) + 1 = 1. 2 leaves the stack; f(1) now has left = 1.
  3. f(1) goes right: f(3) → left: f(4) → its children give 0 and 0 → returns 1. f(3) has left = 1.
  4. f(3) goes right: f(5) → left: f(6) → children 0, 0 → returns 1. f(5) has left = 1.
  5. f(5) right: f(None) → 0. f(5) returns max(1, 0) + 1 = 2 to f(3), as its right value. (In the video she first says "six's right", then corrects herself: it's 3's right.)
  6. f(3) has left 1, right 2 → max + 1 = 3 → f(1)'s right = 3.
  7. f(1): max(1, 3) + 1 = 4. The stack is empty; 4 goes back to the caller ✓
step 1
f(1)f(2)
step 4 (deepest)
f(1)f(3)f(5)f(6) → 1
step 6
f(1)f(3) → 3

9Complexity & remember

Remember max depth with DFSNone → 0 · max(left, right) + 1. Max, not sum: a node reports only one path.

Part C · Minimum depth with DFS

LeetCode 111 · Minimum Depth of Binary Tree

1The question in simple words

Return the number of nodes on the shortest path from the root down to a leaf. Remember, a leaf is a node with no children.

example 1 → 2
      3
     / \
    9   20       9 is a leaf: 3 → 9 = 2 nodes
       /  \
      15   7
example 2 → 5
  2
   \
    3
     \
      4
       \
        5
         \
          6       the only leaf is 6: 5 nodes

2What the constraints tell us

Doubt: anything special about recursion in Python here?
→ A chain like example 2 but with thousands of nodes means thousands of nested calls. Python's default recursion limit is about 1000, so a very deep skewed tree can raise RecursionError. BFS (Part D) has no such limit. Good to mention in an interview.

3Intuition: first try the obvious copy

Minimum depth sounds like max depth with max swapped for min. The teacher does exactly that: copies the max depth code, renames it, changes max to min, and runs it.

wrong first try: just change max to min
class Solution:
    def minDepth(self, root):
        if root is None:
            return 0
        left = self.minDepth(root.left)
        right = self.minDepth(root.right)
        return min(left, right) + 1         # WRONG when one child is missing

Example 1 passes (2). Example 2 fails: it returns 1, but the answer is 5.

4Building the logic: why it fails, and the fix

What goes wrong on example 2

Stand at the root 2. Its left child is None, so the left call returns 0. The right side (3, 4, 5, 6) returns 4. The code returns min(0, 4) + 1 = 1. It's acting as if the path "2 → nothing" ended at a leaf after 1 node.

But a missing child is not a leaf. A leaf is a real node with no children. Root 2 has a child, so 2 is not a leaf, and no path can stop at 2. The 0 from the empty side is not a path at all, so it must not win the min.

Doubt 1: why didn't the same thing break max depth?
→ In max, a 0 from an empty side can never win when the other side is bigger, so it's ignored automatically. And when both sides are 0, the node really is a leaf, so 0 + 1 = 1 is correct. Only min is fooled, because it prefers the smallest number, and 0 is the smallest.

The fix: only compare when both children exist

Doubt 2: what happens at a leaf, where both children are None?
→ The first check fires (left is None) and returns minDepth(None) + 1 = 0 + 1 = 1. That's right: a leaf alone is a path of 1 node. So we don't need a separate leaf check.
Doubt 3: do we still need if root is None: return 0?
→ Yes. It handles the empty tree (answer 0), and it's what the one-child cases call into, like minDepth(None) + 1 at a leaf.

5Approach steps

  1. If the node is None, return 0.
  2. If the left child is None, return minDepth(right) + 1.
  3. If the right child is None, return minDepth(left) + 1.
  4. Otherwise return min(minDepth(left), minDepth(right)) + 1.

6Code (Python)

Minimum depth with DFS (fixed)
class Solution:
    def minDepth(self, root):
        if root is None:
            return 0
        if root.left is None:               # only the right side has paths
            return self.minDepth(root.right) + 1
        if root.right is None:              # only the left side has paths
            return self.minDepth(root.left) + 1
        left = self.minDepth(root.left)     # both exist: compare them
        right = self.minDepth(root.right)
        return min(left, right) + 1

7Code line by line

linewhat it means
if root is None: return 0Empty tree, or the empty side used by the next two checks.
if root.left is None: return self.minDepth(root.right) + 1No left child → the nearest leaf must be on the right. This also covers leaves (gives 1).
if root.right is None: return self.minDepth(root.left) + 1No right child → the nearest leaf must be on the left.
return min(left, right) + 1Both sides are real; take the shorter one, plus this node.

8Dry run on example 2

  1. f(2): left is None → return f(3) + 1. (The wrong version would have used the 0 here.)
  2. f(3): left is None → return f(4) + 1.
  3. f(4): left is None → f(5) + 1. f(5): left is None → f(6) + 1.
  4. f(6): a leaf → left is None → f(None) + 1 = 0 + 1 = 1.
  5. Coming back up: f(5) = 2, f(4) = 3, f(3) = 4, f(2) = 5 ✓
deepest point
f(2)f(3)f(4)f(5)f(6) → 1

On example 1: f(3) has both children → min(f(9), f(20)) + 1. f(9) is a leaf → 1. f(20) → both children are leaves → min(1, 1) + 1 = 2. So f(3) = min(1, 2) + 1 = 2 ✓

9Complexity & remember

Remember min depth with DFSA missing child is not a leaf. One child missing → go to the other side + 1. Use min only when both children exist.

Part D · Minimum depth with BFS

1The question, and 2 the constraints

Same as Part C. 0 nodes allowed, so BFS needs the empty check: return 0.

3Intuition: the first leaf you meet is the answer

The teacher says this is even easier with BFS. BFS visits the tree level by level, from the top. So the first leaf it pops is on the shallowest level that has any leaf. Return that level's number right there, and don't look any further.

Her example: take example 2's chain and add one extra leaf, 5, as the left child of the root:

      2             level 1
     / \
    5   3           level 2   ← 5 is a leaf → answer 2
         \
          4
           \
            5
             \
              6     (the chain's leaf, level 5)

Pop 2: not a leaf, push 5 and 3. Next level: pop 5: leaf → return 2. We never even look at 4, the second 5, or 6. Without the leaf check, BFS would keep going all the way down to 6 and count 5 levels, which is the maximum depth.

For the plain chain in example 2, the only leaf is 6 at the bottom, so here the minimum and maximum depth are the same: 5.

4Building the logic: edit max depth BFS

Doubt: why check for a leaf right after popping, and not when pushing?
→ Checking when we pop means we check each node while we're on its level, so depth is that node's level. Either place can work if you're careful with the counting, but this way is the simplest. Also, this BFS can be faster than DFS for min depth: it stops at the first leaf, while DFS has to explore every branch before it knows the minimum.

5Approach steps

  1. If the root is None, return 0.
  2. Queue = [root], depth = 1.
  3. While the queue isn't empty: pop the whole level. For each node, if it's a leaf, return depth; else push its children.
  4. After each level, depth += 1.

6Code (Python)

Minimum depth with BFS: stop at the first leaf
from collections import deque

class Solution:
    def minDepth(self, root):
        if root is None:
            return 0
        queue = deque([root])
        depth = 1                           # the root is level 1
        while queue:
            size = len(queue)
            for i in range(size):
                node = queue.popleft()
                if node.left is None and node.right is None:
                    return depth            # first leaf = shallowest leaf
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
            depth += 1
        return depth                        # never reached for a real tree

7Code line by line

linewhat it means
depth = 1We start on the root's level, which counts as 1.
if node.left is None and node.right is None: return depthA leaf on this level. Since BFS goes top-down, no leaf can be higher, so this is the answer.
queue.append(node.left / right)Not a leaf → its children go on to the next level.
depth += 1The whole level had no leaf; move one level down.

8Dry run

Her tree with the extra leaf 5

depth 12pop 2 → has children → push 5, 3
depth 253pop 5 → no left, no right → return 2 (3 is never even popped)

LeetCode example 1

depth 13pop 3 → push 9, 20
depth 2920pop 9 → leaf → return 2

Example 2 (the chain)

depth 1–42, 3, 4, 5 each have one child → one node per level, depth goes up to 5
depth 56pop 6 → leaf → return 5

9Complexity & remember

Remember min depth with BFSStart depth = 1. Pop a node: if it's a leaf, return depth. The first leaf BFS meets is the shallowest one.

Part E · Revision page

max depthmin depth
meaninglongest root-to-leaf path (number of levels)shortest root-to-leaf path
BFScount every level, depth starts at 0stop at the first leaf, depth starts at 1
DFS base caseNone → 0None → 0
DFS combinemax(l, r) + 1 alwaysone child missing → other side + 1; else min(l, r) + 1
why the differencea 0 never wins a max unless it's a real leafa 0 from a missing child would win a min, wrongly
time / spaceO(n) / BFS O(n), DFS O(height)O(n) / BFS O(n), DFS O(height)
BFSDFS
empty-tree checkneeded only if 0 nodes allowedalways (base case)
extra memorywidest level (≈ n/2 for a full tree)tallest path (log n balanced, n skewed)
good formin depth (stops at the first leaf)max depth (short, clean code)
If you remember only 5 lines 1. Max depth = number of levels = height.
2. BFS max: depth += 1 after each level.
3. DFS max: None → 0, max(left, right) + 1.
4. DFS min: if one child is missing, go to the other side; min only when both exist.
5. BFS min: the first leaf you pop gives the answer.
Mistakes to avoid ✗ changing max to min and nothing else (fails on one-child nodes)
✗ treating None as a leaf
✗ depth += 1 inside the inner loop (counts nodes, not levels)
✗ starting BFS min depth at 0
✗ adding left and right heights instead of taking the max
test it yourself (paste under a maxDepth and a minDepth solution)
t = TreeNode(1, TreeNode(2), TreeNode(3, TreeNode(4), TreeNode(5, TreeNode(6))))
ex1 = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
chain = TreeNode(2, None, TreeNode(3, None, TreeNode(4, None, TreeNode(5, None, TreeNode(6)))))

s = Solution()
print(s.maxDepth(t))       # 4
print(s.maxDepth(ex1))     # 3
print(s.minDepth(ex1))     # 2
print(s.minDepth(chain))   # 5
print(s.minDepth(None))    # 0

Based on this video: Maximum & Minimum Depth of Binary Tree | DFS & BFS