DSA sheet · Trees · Lowest Common Ancestor pattern

Lowest Common Ancestor of Deepest Leaves

This is the second problem of the LCA pattern. The teacher asks you to first watch the LCA of a Binary Tree video (Problem 21), because this one grows out of it. The twist: nobody gives us p and q. We have to find the deepest nodes ourselves, and there might be 2, 4 or more of them. The teacher first shows the idea that comes to mind straight away (BFS + the old LCA function) and why it's clumsy. Then she builds, step by step, a single DFS that returns two things at once: a node and a height. This "return a pair from the recursion" trick is very useful in many tree problems.

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

Part 0 · Before starting

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

  deepest leaves = 7 and 4   →   answer = 2
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

Part A · First idea: BFS to the last level + repeated LCA

LeetCode 1123 (the same problem as LeetCode 865, Smallest Subtree with all the Deepest Nodes)

1The question in simple words

You get the root of a binary tree. Find all the nodes on the deepest level, and return their lowest common ancestor: the lowest node whose subtree contains all of them.

In the tree above, the deepest level holds 7 and 4. The lowest node above both is 2.

How is it different from Problem 21? There, we were given exactly two nodes, p and q. Here we must discover the nodes ourselves, and there may be more than two.

2What the constraints tell us

3Intuition: the idea that comes first

"Deepest level" should immediately remind you of level order traversal (BFS). Walk the tree level by level, and the nodes in the last level are the deepest leaves. Then reuse the LCA function from Problem 21 on them.

The teacher says this idea should come to mind if you know BFS and DFS. It's not the optimal one, but it's a good first step.

4Building it, and where it gets clumsy

Easy case: exactly two deepest leaves

In our tree, BFS gives the last level [7, 4]. Call LCA(root, 7, 4) → 2. Done. That's just Problem 21 with p = 7 and q = 4.

Hard case: more than two deepest leaves

            3
          /   \
         5     1
        / \   / \
       6   2 0   8

  deepest leaves = 6, 2, 0, 8   →   answer = 3

Now the last level has four nodes. Which one is p, and which one is q? LCA only takes two at a time. So we have to do it in rounds:

Doubt 1: in the video, LCA(5, 1) is said to be 1. Is that right?
→ No, that's a slip. 5 and 1 are on different sides of 3, so their LCA is 3 (exactly the first example of Problem 21). The final answer for this tree is 3.

This is why the teacher calls the BFS idea not optimal: you have to call the LCA function again and again, and each call walks the whole tree. She doesn't code it in the video, because it's just two earlier problems glued together. We code it below so you can test it and see the shape.

Doubt 2: do I really need all those rounds?
→ (Our own shortcut, not from the video.) No. On the last level, the leftmost and the rightmost nodes are the furthest apart. Every other deepest node sits between them, so it's already inside their LCA's subtree. So one call, LCA(first, last), is enough, which makes this approach O(n). The rounds version below still works, and it matches the teacher's explanation.

5Approach steps

  1. BFS level by level. Remember the nodes of the last level you process.
  2. While more than one node is left: pair them up (1st with 2nd, 3rd with 4th, …) and replace each pair with its LCA. An odd one out just moves to the next round.
  3. The single node left is the answer.

6Code (Python)

Brute force: BFS last level + LCA in rounds
from collections import deque

class Solution:
    def lcaDeepestLeaves(self, root):
        if root is None:
            return None
        queue = deque([root])
        last = []
        while queue:                        # level order traversal
            last = list(queue)              # nodes of this level
            for _ in range(len(queue)):
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)

        while len(last) > 1:                # pair up until one is left
            nxt = []
            for i in range(0, len(last), 2):
                if i + 1 < len(last):
                    nxt.append(self.lca(root, last[i], last[i + 1]))
                else:
                    nxt.append(last[i])     # odd one out waits
            last = nxt
        return last[0]

    def lca(self, root, p, q):              # Problem 21, unchanged
        if root is None or root is p or root is q:
            return root
        left = self.lca(root.left, p, q)
        right = self.lca(root.right, p, q)
        if left and right:
            return root
        return left if left else right

7Code line by line

linewhat it means
last = list(queue)At the start of each round, the queue holds exactly one level. Copy it. After the loop ends, last holds the deepest level.
for _ in range(len(queue)):Pop exactly this level's nodes, and push their children (the next level).
while len(last) > 1:Several deepest nodes are left, so keep merging them.
self.lca(root, last[i], last[i + 1])Problem 21's function on a pair. It walks the whole tree each time, and this is what makes the approach slow.
nxt.append(last[i])With an odd count, the last node has no partner this round. It moves on unchanged.
return last[0]The one node left covers all the deepest leaves. If there was only one deepest leaf to begin with, it's the answer itself.

8Dry run (the four-leaf tree)

level 03last = [3] → push 5, 1
level 151last = [5, 1] → push 6, 2, 0, 8
level 26208last = [6, 2, 0, 8] → no children → queue empty
round 1LCA(6, 2) = 5, LCA(0, 8) = 1 → last = [5, 1]
round 2LCA(5, 1) = 3 → last = [3]
endone node left → return 3 ✓

On the first tree: last = [7, 4] → LCA(7, 4) = 2 ✓ in one round.

9Complexity & remember

Remember the first idea"Deepest" → BFS, take the last level → glue them together with Problem 21's LCA, two at a time. It works, but it repeats a lot of work.

Part B · Optimal: one DFS returning (node, height)

1The question (same as Part A)

Same input and output. Now we want to solve it in one pass over the tree.

2What the constraints tell us

The tree has at least 1 node, but the recursion still reaches None children, so we need a base case for None. n ≤ 1000 also means Python's recursion limit (about 1000) is fine in normal cases.

3Intuition: height tells you which side to go

Stand at the root 3 and look at both sides. The left subtree has height 3 (3 → 5 → 2 → 7 has 3 nodes below 3). The right subtree has height only 2. The deepest leaves are always on the taller side. So the answer must be somewhere on the left.

But going top-down we don't know the heights yet. DFS naturally works bottom-up: it hits the None case first, and builds answers on the way back up. So the teacher's plan is to build the answer at every node, from the bottom to the top.

4Building the conditions from the example

Start with only the height

DFS goes 3 → 5 → 6 first. 6 is the first leaf we hit. Its left and right are None, which have height 0. So:

Base case (height only, for now)if root is None: return 0

Equal heights → this node is the LCA (so far)

Look at 7: left and right heights are equal (0 and 0). So 7's best answer so far is 7 itself. Same for 4, and same for 6.

Look at 2: left height 1, right height 1, equal. There are deepest leaves on both sides of 2 (7 and 4), so 2 is the LCA of its subtree's deepest leaves.

The important idea: we don't know yet which subtree will end up being the deepest one, so every node just computes its best answer so far and hands it upwards.

Different heights → keep the taller side's answer

Now 5 gets: from the left (6) height 1, from the right (2) height 2. The right side is deeper, so the deepest leaves of 5's subtree are on the right. The LCA must come from the right, and that's 2. 5 must not claim the answer for itself.

But wait: if 5 only receives heights, how does it know that "the right side's answer is 2"? It doesn't. So each call must send up two things:

Return a pairEach call returns (lca_so_far, height).
6 returns (6, 1). 2 returns (2, 2). 5 compares 1 vs 2 and passes on (2, …).
Doubt 3: how do I return two values?
→ The teacher notes that Java can't return two values directly, so there you write a small Pair class (with .node and .height), and C++ has a built-in pair. In Python it's easy: just return a tuple (node, height) and unpack it.

What height do we send up?

The height always counts the current node too, so it's (height of the side we chose) + 1:

At 5: right taller → (2, 2 + 1) = (2, 3). Notice that 5 is no longer part of the answer, but it still counts in the height. The answer comes from below, while the height measures the full path through 5.

Doubt 4: when the left side is taller, which height do I add 1 to?
→ The left height (the taller side). The answer and the height must come from the same side. In the video the teacher first says "right height + 1" for this case and then corrects it to left height + 1. A good spot to slow down: whichever side wins gives both the node and the height.
Doubt 5: once a lower node is chosen, why doesn't a parent above replace it?
→ Because a parent only takes over when both its sides are equally tall. If one side is taller, the LCA has already been found lower down, and every node above it would be a grandparent or great-grandparent, so not the lowest. They just pass it along.

The base case, final form

Since we now return pairs, the empty spot must return a pair too: no node, height 0.

Base caseif root is None: return (None, 0)

The final answer

The top call dfs(root) returns a pair like (2, 4). We only need the node, so we return dfs(root)[0]. The height is just a helper.

5Approach steps

  1. dfs(None) returns (None, 0).
  2. For a real node: get (left_node, left_h) from the left child and (right_node, right_h) from the right child.
  3. If left_h == right_h → return (root, left_h + 1).
  4. If left_h > right_h → return (left_node, left_h + 1).
  5. Otherwise → return (right_node, right_h + 1).
  6. The answer is the node part of dfs(root).

6Code (Python)

LCA of Deepest Leaves: one DFS returning (node, height)
class Solution:
    def lcaDeepestLeaves(self, root):
        return self.dfs(root)[0]          # we only need the node

    def dfs(self, root):
        if root is None:
            return (None, 0)              # no node, height 0

        left_node, left_h = self.dfs(root.left)
        right_node, right_h = self.dfs(root.right)

        if left_h == right_h:             # deepest leaves on both sides
            return (root, left_h + 1)
        if left_h > right_h:              # deepest leaves only on the left
            return (left_node, left_h + 1)
        return (right_node, right_h + 1)  # deepest leaves only on the right

7Code line by line

linewhat it means
return self.dfs(root)[0]The helper returns (node, height). The question only wants the node.
if root is None: return (None, 0)Base case. An empty spot has no LCA and no height. This also makes a leaf see "0 and 0", so the leaf picks itself.
left_node, left_h = self.dfs(root.left)From the left subtree: the LCA of its deepest leaves, and how tall it is.
right_node, right_h = self.dfs(root.right)Same for the right subtree.
if left_h == right_h: return (root, left_h + 1)Equally deep on both sides → deepest leaves on both sides → this node covers them all. +1 counts this node in the height.
if left_h > right_h: return (left_node, left_h + 1)The left side is deeper → its LCA is the answer for this subtree too. Pass it up, with the left height + 1.
return (right_node, right_h + 1)The only case left: the right side is deeper. No else needed, because the two ifs above already returned.

8Dry run using the call stack

On the main tree. Each line shows what a call returns, in the order DFS finishes them.

  1. dfs(6): left (None, 0), right (None, 0) → equal → returns (6, 1) to 5's left.
  2. 5 goes right → 2 → 7. dfs(7): 0 and 0 → equal → (7, 1).
  3. dfs(4): 0 and 0 → equal → (4, 1).
  4. dfs(2): left (7, 1), right (4, 1) → 1 = 1 → (2, 2). 2 claims the answer.
  5. dfs(5): left (6, 1), right (2, 2) → right taller → (2, 3). 5 passes on 2.
  6. 3 goes right → 1. dfs(0) → (0, 1), dfs(8) → (8, 1).
  7. dfs(1): (0, 1) and (8, 1) → equal → (1, 2).
  8. dfs(3): left (2, 3), right (1, 2) → left taller → (2, 4).
  9. lcaDeepestLeaves returns the node part → 2 ✓
step 1
dfs(3)dfs(5)dfs(6) → (6,1)
step 2 (deepest)
dfs(3)dfs(5) left=(6,1)dfs(2)dfs(7) → (7,1)
step 5
dfs(3)dfs(5) → (2,3)
step 8
dfs(3) → (2,4)

The stack never holds more than one path at a time. 3, 5 and 6 were on it together, and only after 6 returned did 2 get pushed, then 7. It never gets taller than the number of levels.

On the four-leaf tree: 6, 2, 0, 8 each return (x, 1). 5 gets 1 = 1 → (5, 2). 1 gets 1 = 1 → (1, 2). 3 gets 2 = 2 → (3, 3) → answer 3 ✓. It's the same answer as Part A, but in one pass.

9Complexity & remember

Remember LCA of Deepest Leaves Return (node, height) from every call. None → (None, 0). Equal heights → (root, h + 1). Otherwise take the taller side's node, with its height + 1. The answer is dfs(root)[0].

Part C · Revision page

Part A · BFS + repeated LCAPart B · DFS with (node, height)
finds the deepest leaves bylevel order, keeping the last levelcomparing subtree heights
combines them bycalling Problem 21's LCA on pairs, in roundsequal heights → root, else the taller side's node
passes over the treemany (one per LCA call)exactly one
timeO(n²) worst caseO(n)
spaceO(n)O(h)
left height vs right heightreturnexample
equal(root, left_h + 1)2 gets (7,1) and (4,1) → (2, 2)
left taller(left_node, left_h + 1)3 gets (2,3) and (1,2) → (2, 4)
right taller(right_node, right_h + 1)5 gets (6,1) and (2,2) → (2, 3)
None(None, 0)children of every leaf
If you remember only 5 lines 1. The deepest leaves are always on the taller side.
2. Equal heights on both sides → the current node is the LCA (so far).
3. So every call returns a pair: (LCA so far, height).
4. Height = winning side's height + 1. The node comes from the same winning side.
5. Base case (None, 0). The answer is the node part of dfs(root).
Mistakes to avoid ✗ returning only the height (the parent then can't know which node won)
✗ taking the node from one side but the height from the other
✗ forgetting the +1 for the current node in the height
✗ letting a parent with unequal sides return itself (it would be a grandparent, not the lowest)
✗ returning the whole pair instead of just the node at the end
✗ in the BFS idea: LCA(5, 1) is 3, not 1
test it yourself (paste under either solution above)
T = TreeNode
root = T(3, T(5, T(6), T(2, T(7), T(4))), T(1, T(0), T(8)))
full = T(3, T(5, T(6), T(2)), T(1, T(0), T(8)))
s = Solution()
print(s.lcaDeepestLeaves(root).val)       # 2
print(s.lcaDeepestLeaves(full).val)       # 3
print(s.lcaDeepestLeaves(T(1)).val)       # 1  (single node)
print(s.lcaDeepestLeaves(T(0, T(1, None, T(2)), T(3))).val)   # 2 (one deepest leaf)

Based on this video: Lowest Common Ancestor of Deepest Leaves