Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Tree Traversal - Understanding how to navigate through tree nodes recursively
  • Depth-First Search (DFS) - Used to compute the height of each node from the bottom up
  • Tree Height Calculation - Understanding the concept of node height (distance to farthest leaf descendant)

1. DFS (Depth-First Search) with sorting

Intuition

Leaves are nodes with no children, and they have height 0. Their parents have height 1, and so on. If we compute the height of each node (where height is the distance to the farthest leaf in its subtree), nodes with the same height should be grouped together. By collecting all (height, value) pairs and sorting by height, we can form the required groups.

Algorithm

  1. Perform a DFS to compute the height of each node.
  2. For each node, calculate its height as max(leftHeight, rightHeight) + 1, where leaf nodes return -1 from null children.
  3. Store each (height, value) pair in a list.
  4. Sort the list by height.
  5. Group nodes with the same height into sublists and return the result.
class Solution:
    def findLeaves(self, root: Optional[TreeNode]) -> List[List[int]]:
        self.pairs = []
        
        def getHeight(node):
            if node is None:
                return -1
            
            # first calculate the height of the left and right children
            leftHeight = getHeight(node.left)
            rightHeight = getHeight(node.right)
            
            # based on the height of the left and right children, obtain the height of the current (parent) node
            currHeight = max(leftHeight, rightHeight) + 1
            
            # collect the pair -> (height, val)
            self.pairs.append((currHeight, node.val))
            
            # return the height of the current node
            return currHeight
        
        getHeight(root)
        
        # sort all the (height, val) pairs
        self.pairs.sort(key=lambda p: p[0])
        
        n = len(self.pairs)
        height = 0
        i = 0
        solution = []
        
        while i < n:
            nums = []
            while i < n and self.pairs[i][0] == height:
                nums.append(self.pairs[i][1])
                i += 1
            solution.append(nums)
            height += 1
        
        return solution

Time & Space Complexity

  • Time complexity: O(NlogN)O(N \log N)
  • Space complexity: O(N)O(N)

Where NN is the total number of nodes in the binary tree


2. DFS (Depth-First Search) without sorting

Intuition

Instead of collecting pairs and sorting, we can directly place each node into the correct group during the DFS. When we compute a node's height, we use that height as an index into the result list. If the list doesn't have enough sublists yet, we create a new one.

Algorithm

  1. Initialize an empty result list.
  2. Perform a DFS to compute the height of each node:
    • If the node is null, return -1.
    • Recursively get the heights of left and right children.
    • Compute current height as max(leftHeight, rightHeight) + 1.
    • If the result list size equals the current height, append a new empty sublist.
    • Add the node's value to result[currentHeight].
    • Return the current height.
  3. Call DFS on the root and return the result.
class Solution:
    def findLeaves(self, root: Optional[TreeNode]) -> List[List[int]]:
        self.solution = []
        
        def getHeight(node):
            if node is None:
                return -1
            
            leftHeight = getHeight(node.left)
            rightHeight = getHeight(node.right)
            currHeight = max(leftHeight, rightHeight) + 1
            
            if len(self.solution) == currHeight:
                self.solution.append([])
            
            self.solution[currHeight].append(node.val)
            return currHeight
        
        getHeight(root)
        return self.solution

Time & Space Complexity

  • Time complexity: O(N)O(N)
  • Space complexity: O(N)O(N)

Where NN is the total number of nodes in the binary tree


Common Pitfalls

Confusing Height with Depth

Height is measured from the node down to its farthest leaf (leaves have height 0), while depth is measured from the root down to the node. This problem requires height-based grouping, not depth. Using depth would group nodes by their distance from the root, which produces incorrect results since leaves at different depths would end up in different groups.

Returning Wrong Base Case Value

The base case for null nodes should return -1, not 0. Since leaves have height 0 (computed as max(-1, -1) + 1), returning 0 for null would incorrectly make leaves have height 1. This off-by-one error shifts all groupings and produces wrong output.

Forgetting to Handle Result List Growth

When directly inserting nodes into the result list by height index, you must check if the list has enough sublists before accessing result[height]. Failing to add a new sublist when result.size() == height causes an index out of bounds error. Always ensure the result list grows dynamically as you encounter nodes with new height values.