Given the root
of a binary tree, collect a tree's nodes as if you were doing this:
Return a list of lists of the collected nodes.
Example 1:
Input: root = [1,2,3,4,5] Output: [[4,5,3],[2],[1]] Explanation: [[4,5,3],[2],[1]]
Example 2:
Input: root = [1] Output: [[1]]
Constraints:
[1, 100]
.-100 <= Node.val <= 100
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Think of peeling off the leaves from a tree, layer by layer. The brute force approach focuses on repeatedly identifying and removing all the leaves from the tree until nothing is left. We collect the leaves at each stage, and that gives us our answer.
Here's how the algorithm would work step-by-step:
def find_leaves_of_binary_tree(root):
leaf_layers = []
while root:
current_leaves = []
new_root = remove_leaves(root, current_leaves)
leaf_layers.append(current_leaves)
root = new_root
return leaf_layers
def remove_leaves(root, current_leaves):
if not root:
return None
if not root.left and not root.right:
current_leaves.append(root.val)
return None
# Recursively remove leaves from the left subtree
root.left = remove_leaves(root.left, current_leaves)
# Recursively remove leaves from the right subtree
root.right = remove_leaves(root.right, current_leaves)
return root
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
The goal is to repeatedly find and remove the leaves (nodes with no children) of a binary tree, collecting them into lists. We can figure out how 'deep' each node is, working from the bottom up, which tells us when it will be a leaf.
Here's how the algorithm would work step-by-step:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def find_leaves(root):
leaves_by_height = []
def get_height(node):
if not node:
return -1
left_height = get_height(node.left)
right_height = get_height(node.right)
# Node height is one greater than max child height.
node_height = max(left_height, right_height) + 1
# Add new height list if needed
if len(leaves_by_height) <= node_height:
leaves_by_height.append([])
# Store nodes at their corresponding height.
leaves_by_height[node_height].append(node.val)
return node_height
get_height(root)
return leaves_by_height
Case | How to Handle |
---|---|
Null or empty tree | Return an empty list immediately as there are no nodes to process. |
Single node tree (root is also a leaf) | Return a list containing a single inner list with the root's value; the next iteration will result in an empty tree. |
Tree with all nodes having the same value | The algorithm should correctly identify and remove leaves regardless of value duplication. |
Completely skewed tree (left or right leaning) | The solution should handle the potentially deep recursion stack without stack overflow, potentially requiring tail-call optimization or iterative solution. |
Tree with a large number of nodes | Ensure the solution is efficient in terms of time and space complexity to avoid exceeding time limits or memory constraints, potentially using iterative removal. |
Tree with negative node values | The algorithm should function correctly with negative node values as node values are treated as data, not indices. |
A tree where some subtrees become empty before others | The height calculation or similar logic in recursive solutions should properly account for subtrees already processed and removed. |
Extreme node values that could lead to integer overflow when summing or comparing in helper functions | Use appropriate data types (e.g., long) to avoid integer overflow if operations on node values are performed. |