Find Largest Value in Each Tree Row
MedPrompt
Given the root of a binary tree, return an array of the largest value in each row of the tree (0-indexed).
Example 1:
Input: root = [1,3,2,5,3,null,9]
Output: [1,3,9]Example 2:
Input: root = [1,2,3]
Output: [1,3]
Constraints:
- The number of nodes in the tree will be in the range
[0, 104]. -231 <= Node.val <= 231 - 1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach traverses the tree level by level using a queue. For each level, it iterates through all the nodes at that level to find the maximum value, then adds it to the result list. This is a very intuitive method for problems involving tree levels.
Algorithm
- Initialize an empty list
result. - If the
rootis null, return the empty list. - Create a
Queueand add therootnode. - While the queue is not empty:
- Get the number of nodes in the current level,
levelSize = queue.size(). - Initialize a variable
maxInLeveltoInteger.MIN_VALUE. - Loop
levelSizetimes:- Dequeue a node
currentNode. - Update
maxInLevelwith the maximum of its current value andcurrentNode.val. - Enqueue the left and right children of
currentNodeif they are not null.
- Dequeue a node
- Add
maxInLevelto theresultlist.
- Get the number of nodes in the current level,
- Return
result.
Walkthrough
We can solve this problem by performing a level order traversal of the tree, which is naturally implemented using a Breadth-First Search (BFS) algorithm with a queue.
The core idea is to process the tree one level at a time. We start by putting the root node in a queue. Then, we enter a loop that continues as long as the queue is not empty. In each iteration of this outer loop, we are processing a single level. We first determine the number of nodes on the current level by checking the queue's size. We then iterate exactly that many times, dequeueing one node at a time. While processing the nodes of a level, we keep track of the maximum value seen so far. After the inner loop finishes, we have the largest value for that level, which we add to our result list. We also add the children of each processed node to the queue, which sets up the next level for the subsequent iteration of the outer loop.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public List<Integer> largestValues(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { int levelSize = queue.size(); int maxInLevel = Integer.MIN_VALUE; for (int i = 0; i < levelSize; i++) { TreeNode currentNode = queue.poll(); maxInLevel = Math.max(maxInLevel, currentNode.val); if (currentNode.left != null) { queue.offer(currentNode.left); } if (currentNode.right != null) { queue.offer(currentNode.right); } } result.add(maxInLevel); } return result; }}Complexity
Time
O(N), where N is the total number of nodes in the tree. Each node is visited, enqueued, and dequeued exactly once.
Space
O(W), where W is the maximum width of the tree. In the worst-case scenario of a complete binary tree, the width can be up to N/2, leading to a space complexity of O(N).
Trade-offs
Pros
Very intuitive for level-by-level tree problems.
Iterative approach avoids recursion depth limits and potential stack overflow on very deep trees.
Cons
Can be less space-efficient than DFS for wide trees (e.g., complete binary trees), as the queue can hold up to O(N) nodes.
This approach uses a recursive Depth-First Search (DFS) traversal. By passing the current level (or depth) as a parameter in the recursion, we can keep track of the maximum value for each level in a results list.
Algorithm
- Initialize an empty list
result. - Create a recursive helper function
dfs(node, level, result). - Start the process by calling
dfs(root, 0, result). - Inside the
dfsfunction:- If the current
nodeis null, return. - Check if the
levelis equal to the current size of theresultlist.- If yes, it's the first node at this level. Add
node.valtoresult. - If no, a value for this level already exists. Update it with
Math.max(result.get(level), node.val).
- If yes, it's the first node at this level. Add
- Recursively call
dfsfor the left child withlevel + 1. - Recursively call
dfsfor the right child withlevel + 1.
- If the current
- Finally, return the
resultlist.
Walkthrough
A DFS approach can also solve this problem elegantly. We can use a pre-order traversal (Node -> Left -> Right) and maintain the current level of the traversal. We use a list, let's call it result, to store the largest value for each level. The index of the list corresponds to the level number.
We define a recursive helper function, dfs(node, level, result). When we visit a node at a given level, we check if this level is new. We can determine this by comparing the level with the current size of the result list. If level equals result.size(), it's the first time we've reached this depth, so we add the node's value to the result list. If level is less than result.size(), it means we've already visited a node at this level, so we simply update the existing maximum value at result.get(level) if the current node's value is greater. After processing the current node, we recursively call the function for its left and right children, incrementing the level by one.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public List<Integer> largestValues(TreeNode root) { List<Integer> result = new ArrayList<>(); dfs(root, 0, result); return result; } private void dfs(TreeNode node, int level, List<Integer> result) { if (node == null) { return; } // If this is the first time we are visiting this level if (level == result.size()) { result.add(node.val); } else { // We have seen this level before, so update the max value result.set(level, Math.max(result.get(level), node.val)); } // Recurse for children at the next level dfs(node.left, level + 1, result); dfs(node.right, level + 1, result); }}Complexity
Time
O(N), where N is the total number of nodes. Each node is visited exactly once.
Space
O(H), where H is the height of the tree, due to the recursion call stack. In a balanced tree, this is O(log N), which is very efficient. In the worst case of a skewed tree, the height is N, leading to O(N) space complexity.
Trade-offs
Pros
More space-efficient than BFS for many common tree structures, especially balanced or near-balanced trees (O(log N) vs O(N)).
The recursive implementation can be very concise and elegant.
Cons
May cause a stack overflow for extremely deep trees (e.g., a skewed tree with many nodes).
The logic might be slightly less direct for a level-based problem compared to BFS.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public List < Integer > largestValues ( TreeNode root ) { List < Integer > ans = new ArrayList <>(); if ( root == null ) { return ans ; } Deque < TreeNode > q = new ArrayDeque <>(); q . offer ( root ); while (! q . isEmpty ()) { int t = q . peek (). val ; for ( int i = q . size (); i > 0 ; -- i ) { TreeNode node = q . poll (); t = Math . max ( t , node . val ); if ( node . left != null ) { q . offer ( node . left ); } if ( node . right != null ) { q . offer ( node . right ); } } ans . add ( t ); } return ans ; } }/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: vector < int > largestValues ( TreeNode * root ) { if ( ! root ) return {}; queue < TreeNode *> q { { root } }; vector < int > ans ; while ( ! q . empty ()) { int t = q . front () -> val ; for ( int i = q . size (); i ; -- i ) { TreeNode * node = q . front (); t = max ( t , node -> val ); q . pop (); if ( node -> left ) q . push ( node -> left ); if ( node -> right ) q . push ( node -> right ); } ans . push_back ( t ); } return ans ; } };# 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 root is None : return [] q = deque ([ root ]) ans = [] while q : t = - inf for _ in range ( len ( q )): node = q . popleft () t = max ( t , node . val ) if node . left : q . append ( node . left ) if node . right : q . append ( node . right ) ans . append ( t ) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.