515. Find Largest Value in Tree Row - Explanation
Description
Return an array containing the largest node value at each depth of the binary tree, ordered from the root downward. The root is at depth 0, its children are at depth 1, and so on. Return an empty array for an empty tree.
Compare all present nodes at the same depth, even when they have different parents. Missing children are not values and do not participate in the maximum.
Examples use level-order arrays. After the root, each present node contributes left and right child entries in queue order. null marks a missing child, and trailing missing entries may be omitted. [] represents an empty tree.
Example 1:
Input: root = [5,9,-2,1,null,7,3]
Output: [5,9,7]Explanation: The rows contain [5], [9,-2], and [1,7,3]. Their largest values are 5, 9, and 7, respectively.
Example 2:
Input: root = [-5,-9,-6]
Output: [-5,-6]Explanation: The root row has maximum -5. The next row contains -9 and -6, so its maximum is -6. Missing nodes do not contribute zero.
Example 3:
Input: root = []
Output: []Explanation: An empty tree has no rows, so the result is an empty array.
Constraints:
- The tree contains between 0 and 10000 nodes.
-2147483648 <= Node.val <= 2147483647
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Tree Traversal - Understanding how to navigate through tree nodes using their left and right children
- Breadth-First Search (BFS) - Used to process nodes level by level using a queue
- Depth-First Search (DFS) - Alternative approach that tracks depth while recursively visiting nodes
1. Breadth First Search
Intuition
To find the largest value in each row, we need to visit all nodes level by level. BFS naturally processes nodes in level order using a queue. For each level, we track the maximum value seen among all nodes at that depth.
Algorithm
- If the root is
null, return an empty list. - Initialize a queue with the root node and an empty result list.
- While the queue is not empty:
- Record the number of nodes at the current level.
- Initialize
rowMaxwith the first node's value. - Process all nodes at this level: dequeue each node, update
rowMaxif needed, and enqueue its children. - Append
rowMaxto the result list.
- Return the result list.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def largestValues(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
q = deque([root])
while q:
row_max = q[0].val
for _ in range(len(q)):
node = q.popleft()
row_max = max(row_max, node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
res.append(row_max)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
2. Depth First Search
Intuition
We can also solve this with DFS by tracking the current depth as we traverse. When we reach a node, if it's the first node at that depth, we add it to the result. Otherwise, we compare it with the existing maximum for that depth and update if necessary.
Algorithm
- Initialize an empty result list.
- Define a recursive DFS function that takes a node and its level:
- If the node is
null, return. - If
levelequals the size of the result list, append the node's value (first node at this depth). - Otherwise, update
res[level]to be the maximum of its current value and the node's value. - Recursively call DFS on the left and right children with
level + 1.
- If the node is
- Call DFS starting from the root at level
0. - Return the result list.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def largestValues(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
def dfs(node, level):
if not node:
return
if level == len(res):
res.append(node.val)
else:
res[level] = max(res[level], node.val)
dfs(node.left, level + 1)
dfs(node.right, level + 1)
dfs(root, 0)
return resTime & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
3. Iterative DFS
Intuition
The recursive DFS can be converted to an iterative approach using a stack. Each stack entry stores both the node and its level. This avoids recursion overhead while maintaining the same logic.
Algorithm
- If the root is
null, return an empty list. - Initialize a stack with
(root, 0)and an empty result list. - While the stack is not empty:
- Pop
(node, level)from the stack. - If
levelequals the result list size, append the node's value. - Otherwise, update
res[level]to the maximum of its current value and the node's value. - Push the right child first (if exists), then the left child, both with
level + 1.
- Pop
- Return the result list.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def largestValues(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [(root, 0)]
while stack:
node, level = stack.pop()
if level == len(res):
res.append(node.val)
else:
res[level] = max(res[level], node.val)
if node.right:
stack.append((node.right, level + 1))
if node.left:
stack.append((node.left, level + 1))
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Not Handling Empty Tree
Forgetting to check if the root is null before processing leads to null pointer exceptions or runtime errors. Always add an early return for the empty tree case, returning an empty list rather than attempting to traverse a non-existent tree.
Initializing Row Maximum Incorrectly
When tracking the maximum value for each row, initializing rowMax to 0 or a small positive number fails for trees with negative values. The correct approach is to initialize rowMax with the first node's value in that row, or use Integer.MIN_VALUE (or equivalent) to ensure negative values are handled correctly.
Mixing Up Level Boundaries in BFS
In BFS, you must process all nodes at the current level before moving to the next. A common mistake is not capturing the queue size before the inner loop, causing nodes from the next level to be processed prematurely. Always store queue.size() at the start of each level iteration and use that fixed count for the inner loop.
Sign in to join the discussion