Binary Tree Level Order Traversal II
MedPrompt
Given the root of a binary tree, return the bottom-up level order traversal of its nodes' values. (i.e., from left to right, level by level from leaf to root).
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: [[15,7],[9,20],[3]]Example 2:
Input: root = [1]
Output: [[1]]Example 3:
Input: root = []
Output: []
Constraints:
- The number of nodes in the tree is in the range
[0, 2000]. -1000 <= Node.val <= 1000
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a recursive Depth-First Search (DFS) to traverse the tree. It keeps track of the level of each node to group them correctly. The levels are gathered in a top-down order, and the final list of levels is reversed to achieve the required bottom-up order.
Algorithm
- Initialize an empty list of lists,
result. - If the root is
null, return the emptyresult. - Create a recursive helper function,
dfs(node, level, result). - Call the helper function with the root, level 0, and the
resultlist. - Inside the helper function:
- Base case: if the node is
null, return. - If
levelis equal toresult.size(), it's the first time we're visiting this level. Add a new empty list toresult. - Add the node's value to the list at index
level:result.get(level).add(node.val). - Recursively call for the left child:
dfs(node.left, level + 1, result). - Recursively call for the right child:
dfs(node.right, level + 1, result).
- Base case: if the node is
- After the initial DFS call returns, the
resultlist contains the levels in top-down order. - Reverse the
resultlist usingCollections.reverse(). - Return the reversed list.
Walkthrough
We can perform a pre-order traversal on the tree while keeping track of the current level. We use a list of lists, result, to store the nodes for each level. The level variable acts as an index into this result list. When we visit a node at a level for the first time, we add a new list to result. Then, we add the node's value to the list corresponding to its level. After the traversal is complete, result will hold the levels from top to bottom. The final step is to reverse this result list to get the bottom-up order.
/** * 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<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) { return result; } dfs(root, 0, result); Collections.reverse(result); return result; } private void dfs(TreeNode node, int level, List<List<Integer>> result) { if (node == null) { return; } // If we are visiting a new level, add a new list for it. if (level >= result.size()) { result.add(new ArrayList<>()); } // Add the node's value to the list for its level. result.get(level).add(node.val); // Recurse for children. dfs(node.left, level + 1, result); dfs(node.right, level + 1, result); }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Conceptually simple if one is familiar with DFS and recursion.
Follows a standard tree traversal pattern.
Cons
Requires an extra step to reverse the final list.
The recursive approach can lead to a
StackOverflowErrorfor very deep or skewed trees.The space complexity for the recursion stack is O(H), where H is the tree height, which can be O(N) for a skewed tree.
This approach performs a standard top-down level order traversal using a queue (which is a Breadth-First Search). The nodes are processed level by level, and each level is stored in a list. After all levels are collected in top-down order, the entire list of levels is reversed.
Algorithm
- Initialize an empty
ArrayListof lists,result. - If the root is
null, return the emptyresult. - Initialize a
Queueand add the root node to it. - While the queue is not empty:
- Get the number of nodes at the current level (
levelSize = queue.size()). - Create a new list
currentLevelto store node values for this level. - Loop
levelSizetimes:- Dequeue a node.
- Add its value to
currentLevel. - If the node has a non-null left child, enqueue it.
- If the node has a non-null right child, enqueue it.
- Add
currentLevelto theresultlist.
- Get the number of nodes at the current level (
- After the loop, reverse the
resultlist usingCollections.reverse(). - Return the reversed list.
Walkthrough
This is a straightforward two-pass approach. The first pass consists of a standard Breadth-First Search (BFS) to get the level order traversal from top to bottom. We use a queue to keep track of nodes to visit. In each iteration of the main loop, we process all nodes at the current level. The values of these nodes are stored in a temporary list, which is then added to our main result list. After the BFS is complete, the result list contains all levels in order from root to leaves. The second pass is to simply reverse this list to meet the problem's requirement of a bottom-up traversal.
/** * 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<List<Integer>> levelOrderBottom(TreeNode root) { List<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(); List<Integer> currentLevel = new ArrayList<>(); for (int i = 0; i < levelSize; i++) { TreeNode node = queue.poll(); currentLevel.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } result.add(currentLevel); } Collections.reverse(result); return result; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
The iterative BFS approach avoids recursion, preventing potential
StackOverflowErroron very deep trees.It's a very common and well-understood pattern for tree problems.
Generally more space-efficient on the auxiliary data structure (queue) for skewed trees compared to DFS's recursion stack.
Cons
Requires an extra pass (or operation) to reverse the list of levels after the traversal is complete.
This is an optimized version of the BFS approach. It performs a standard level order traversal but cleverly builds the result list in the desired bottom-up order from the start. Instead of appending each level's list to the end and reversing later, it prepends each level's list to the front of the result list.
Algorithm
- Initialize an empty
LinkedListof lists,result. Using aLinkedListis crucial for efficient prepending. - If the root is
null, return the emptyresult. - Initialize a
Queueand add the root node to it. - While the queue is not empty:
- Get the number of nodes at the current level (
levelSize = queue.size()). - Create a new list
currentLevelto store node values for this level. - Loop
levelSizetimes:- Dequeue a node.
- Add its value to
currentLevel. - If the node has a non-null left child, enqueue it.
- If the node has a non-null right child, enqueue it.
- Add
currentLevelto the beginning of theresultlist usingresult.add(0, currentLevel).
- Get the number of nodes at the current level (
- After the loop, the
resultlist is already in bottom-up order. Return it.
Walkthrough
This approach refines the standard BFS traversal by eliminating the final reversal step. We still process the tree level by level from top to bottom using a queue. However, when we finish processing a level, we add its corresponding list of values to the beginning of our result list. To make this prepending operation efficient (O(1)), the result list should be implemented as a LinkedList. By always adding new levels to the front, the list naturally gets built in reverse order, resulting in the desired bottom-up traversal directly.
/** * 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<List<Integer>> levelOrderBottom(TreeNode root) { // Use LinkedList for efficient prepending (addFirst or add(0, ...)) List<List<Integer>> result = new LinkedList<>(); if (root == null) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { int levelSize = queue.size(); List<Integer> currentLevel = new ArrayList<>(); for (int i = 0; i < levelSize; i++) { TreeNode node = queue.poll(); currentLevel.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } // Add the level at the beginning of the list result.add(0, currentLevel); } return result; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Builds the result in the correct bottom-up order directly, avoiding a final reversal step.
It's an elegant single-pass solution (over the nodes).
Iterative, so no risk of stack overflow.
Cons
Using a
LinkedListfor the result list might have slightly higher memory overhead and worse cache performance compared to anArrayList, though the asymptotic complexity remains the same.
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 < List < Integer >> levelOrderBottom ( TreeNode root ) { LinkedList < List < Integer >> ans = new LinkedList <>(); if ( root == null ) { return ans ; } Deque < TreeNode > q = new LinkedList <>(); q . offerLast ( root ); while (! q . isEmpty ()) { List < Integer > t = new ArrayList <>(); for ( int i = q . size (); i > 0 ; -- i ) { TreeNode node = q . pollFirst (); t . add ( node . val ); if ( node . left != null ) { q . offerLast ( node . left ); } if ( node . right != null ) { q . offerLast ( node . right ); } } ans . addFirst ( t ); } return ans ; } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number[][]} */ var levelOrderBottom = function (root) { const ans = []; if (!root) return ans; const q = [root]; while (q.length) { const t = []; for (let i = q.length; i > 0; --i) { const node = q.shift(); t.push(node.val); if (node.left) q.push(node.left); if (node.right) q.push(node.right); } ans.unshift(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 < vector < int >> levelOrderBottom ( TreeNode * root ) { vector < vector < int >> ans ; if ( ! root ) return ans ; queue < TreeNode *> q { { root } }; while ( ! q . empty ()) { vector < int > t ; for ( int i = q . size (); i ; -- i ) { auto node = q . front (); q . pop (); t . emplace_back ( node -> val ); if ( node -> left ) q . push ( node -> left ); if ( node -> right ) q . push ( node -> right ); } ans . emplace_back ( t ); } reverse ( ans . begin (), ans . end ()); 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 levelOrderBottom ( self , root : Optional [ TreeNode ]) -> List [ List [ int ]]: ans = [] if root is None : return ans q = deque ([ root ]) while q : t = [] for _ in range ( len ( q )): node = q . popleft () t . append ( node . val ) if node . left : q . append ( node . left ) if node . right : q . append ( node . right ) ans . append ( t ) return ans [:: - 1 ]Video 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.