Description
145. Binary Tree Postorder Traversal
Given the root of a binary tree, return the postorder traversal of its nodes’ values.
Example 1:
Input: root = [1,null,2,3]
Output: [3,2,1]
Explanation:

Example 2:
Input: root = [1,2,3,4,5,null,8,null,null,6,7,9]
Output: [4,6,7,5,2,9,8,3,1]
Explanation:

Example 3:
Input: root = []
Output: []
Example 4:
Input: root = [1]
Output: [1]
Constraints:
- The number of nodes in the tree is in the range .
Approach 1: Recursive Traversal ( Time, Space)
Intuition
Traverse the binary tree recursively following the postorder sequence: Left subtree Right subtree Root node. Recursively visit the left child, then the right child, and append the current node’s value.
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
postorder(root, result);
return result;
}
private void postorder(TreeNode node, List<Integer> result) {
if (node == null) return;
postorder(node.left, result);
postorder(node.right, result);
result.add(node.val);
}
}
Complexity
- Time Complexity: — Every node in the binary tree is visited once.
- Space Complexity: worst-case call stack depth for a skewed tree ( for a balanced tree).
Approach 2: Iterative Stack (Unified Template — Time, Space)
Intuition
This uses the exact same unified template as iterative Preorder and Inorder (curr pointer + nested while loops with ArrayDeque).
Postorder () is the exact reverse of a mirrored Preorder (). By traversing Right before Left and prepending values (addFirst), we maintain the exact same structural template as Preorder while generating the Postorder sequence.
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<Integer> result = new LinkedList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
// Explore right branch first and prepend values on the way down
while (curr != null) {
result.addFirst(curr.val); // Prepend: Reverses Root -> Right -> Left into Left -> Right -> Root
stack.push(curr);
curr = curr.right; // Move RIGHT instead of Left
}
// Backtrack and step into the left subtree
curr = stack.pop();
curr = curr.left; // Move LEFT after popping
}
return result;
}
}
1. Why LinkedList instead of ArrayList here?
Because we are inserting elements at the very front (index 0) to reverse the traversal order on the fly:
LinkedList.addFirst(val)takes constant time (just updates pointers).ArrayList.add(0, val)takes linear time (must shift all existing elements to the right every time).
Using ArrayList with add(0, val) would degrade the overall time complexity from to .
2. Difference Between ArrayList and LinkedList
| Feature | ArrayList | LinkedList |
|---|---|---|
| Underlying Data Structure | Resizable Array | Doubly Linked Nodes |
| Insert/Delete at Ends | at front, at end | at both front and end |
Random Access (get(i)) | ||
| Memory Overhead | Lower (contiguous block) | Higher (stores pointers for each node) |
3. addFirst() vs. Regular add()
add(val): Appendsvalto the end of the list.- Example:
[1, 2]add(3)[1, 2, 3]
- Example:
addFirst(val): Insertsvalat the front (index0) of the list.- Example:
[1, 2]addFirst(3)[3, 1, 2]
- Example:
Complexity
- Time Complexity: — Each node is pushed, popped, and prepended to the linked list in time.
- Space Complexity: — Auxiliary space required for the explicit stack.
Approach 3: Morris Postorder Traversal (Unified Mirror Template — Time, Space)
Intuition
Mirror the standard Morris Preorder Traversal by creating temporary threads on the right side:
- Find the in-order successor (leftmost node in the right subtree).
- Create/destroy threads on
succ.leftinstead ofpred.right. - Prepend
curr.valto the result list when creating a new thread (succ.left == null) or when a node has no right child.
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
LinkedList<Integer> result = new LinkedList<>();
TreeNode curr = root;
while (curr != null) {
if (curr.right == null) {
result.addFirst(curr.val); // Prepend current node
curr = curr.left;
} else {
// Find in-order successor (leftmost node in right subtree)
TreeNode succ = curr.right;
while (succ.left != null && succ.left != curr) {
succ = succ.left;
}
if (succ.left == null) {
result.addFirst(curr.val); // Visit root BEFORE creating thread and moving right
succ.left = curr; // Temporary thread back to curr
curr = curr.right;
} else {
succ.left = null; // Restore original tree structure
curr = curr.left;
}
}
}
return result;
}
}
Complexity
- Time Complexity: — Each edge is traversed at most twice.
- Space Complexity: auxiliary space — Pointers are modified temporarily without stack frames.
Breakdown: What Changed Across Traversals & How It Works
Comparison of the Unified Iterative Template
| Traversal | Target Order | Traversal Order | Result Operation | Loop Movements |
|---|---|---|---|---|
| Preorder | Append (add) on way down | Go .left in inner loop, .right after pop | ||
| Inorder | Append (add) after pop | Go .left in inner loop, .right after pop | ||
| Postorder | Prepend (addFirst) on way down | Swap sides: Go .right in inner loop, .left after pop |
Key Changes Explained
1. Recursive Approach
- The Change:
result.add(node.val)is moved to the end of the method, after both recursive callspostorder(node.left)andpostorder(node.right). - How It Works: Ensures both left and right child subtrees complete their executions before the parent node’s value is appended to the list.
2. Iterative Stack Approach
-
The Change: Three minor symmetric modifications to the Preorder code:
- Change
result.add()toresult.addFirst()(prepend). - Change
curr = curr.lefttocurr = curr.rightinside the innerwhileloop. - Change
curr = curr.righttocurr = curr.leftafterstack.pop().
- Change
-
How It Works: By traversing
RightbeforeLeft, the nodes are visited in Reverse Postorder (). Prepending each node to the head of the output list automatically reverses this sequence back into standard Postorder ().
3. Morris Traversal Approach
-
The Change: Mirroring Morris Preorder:
- Look for
succ(leftmost child ofcurr.right) instead ofpred(rightmost child ofcurr.left). - Thread
succ.left = currinstead ofpred.right = curr. - Prepend
addFirstinstead of appendingadd.
- Look for
-
How It Works: Uses the right child links to traverse downward, temporarily wiring the leftmost leaf’s
leftpointer back tocurr. Prepending the output converts the traversal directly into Postorder without requiring auxiliary stack memory or post-processing reversals.