Topics › Binary Trees › Traversals
Postorder Traversal
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Given the root of a binary tree, return the values of its nodes in postorder: first the entire left subtree in postorder, then the entire right subtree in postorder, and only then the node itself. An empty tree has an empty traversal.
Constraints: between 0 and 10^5 nodes here (LeetCode uses at most 100), arbitrary 32-bit values, duplicates allowed.
Trees are written in LeetCode’s level-order form, where null marks a missing child.
Example 1
1
\
2
/
3
root = [1, null, 2, 3]
answer = [3, 2, 1] // 3 is finished first, then 2 (its subtree is done), then the root
Example 2
1
/ \
2 3
/ \ / \
4 5 6 7
root = [1, 2, 3, 4, 5, 6, 7]
answer = [4, 5, 2, 6, 7, 3, 1] // every node appears after all of its descendants; the root is last
Postorder is the order to use whenever a node’s answer depends on its children’s answers: computing heights and sizes, freeing or deleting a tree, evaluating an expression tree. By the time a node is processed, both of its subtrees have already been handled.
Approach 1: Recursion
Intuition. The definition is recursive, so the code is too: traverse the left subtree, traverse the right subtree, then record the node. One shared list collects the output so that no call has to build and concatenate its own list.
Algorithm.
- If the node is
null, return. - Traverse the left subtree, then the right subtree.
- Append the node’s value.
static List<Integer> recursive(TreeNode root) {
List<Integer> out = new ArrayList<>();
postorder(root, out);
return out;
}
private static void postorder(TreeNode node, List<Integer> out) {
if (node == null) return; // empty subtree: nothing to visit
postorder(node.left, out); // Left
postorder(node.right, out); // Right
out.add(node.val); // Root, only after both subtrees are finished
}
Why it works. By induction on the subtree size: the first two calls append exactly the left and right subtrees in postorder, and the node is appended after both, which is the definition.
Complexity. Time O(n): constant work per node plus n + 1 calls on null children. Space O(h) for the call stack, which becomes O(n) on a chain; a chain of 10^5 nodes can overflow Java’s default stack.
Postorder is the hardest of the three orders to make iterative, because a node is passed twice before it can be emitted: once on the way down into its left subtree and once on the way from the left subtree into the right subtree. The next two approaches handle that in two different ways.
Approach 2: Reverse a modified preorder
Intuition. Write postorder backwards: Left, Right, Root reversed is Root, Right, Left. That is just preorder with the two children swapped, and preorder is easy with a stack because a node can be emitted the moment it is popped. So we generate Root, Right, Left with a stack (pushing the left child first so the right child is popped first), then reverse the list.
Why the reversal is exact and not just “roughly right”: by induction, the stack walk emits the root, then the right subtree in Root-Right-Left order, then the left subtree in Root-Right-Left order. Reversing a concatenation reverses the order of the pieces and reverses each piece, which gives the left subtree in postorder, the right subtree in postorder, then the root.
Algorithm.
- If the root is
null, return an empty list. - Push the root. While the stack is not empty: pop a node, append it, push its left child, then its right child (each only if present).
- Reverse the list.
static List<Integer> reversedPreorder(TreeNode root) {
List<Integer> out = new ArrayList<>();
if (root == null) return out;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
out.add(node.val); // builds Root, Right, Left
if (node.left != null) stack.push(node.left); // left pushed first so right is popped first
if (node.right != null) stack.push(node.right);
}
Collections.reverse(out); // reverse of Root-Right-Left is Left-Right-Root
return out;
}
On Example 2 the stack walk produces [1, 3, 7, 6, 2, 5, 4], and reversing gives [4, 5, 2, 6, 7, 3, 1].
Complexity. Time O(n): one push and one pop per node, and the reversal is linear. Space O(h) for the stack, but the method cannot hand out any value until the whole traversal is done, because the first postorder value is the last one generated. When that matters (streaming, or stopping early), use Approach 3. The classic “two stacks” formulation of this same idea uses a second stack in place of the reversal.
Approach 3: One stack with a last-visited pointer
Intuition. Use the same stack shape as iterative inorder: walk left, pushing every node. When we cannot go further left, look at (but do not pop) the top node. Its left subtree is done. Two cases remain:
- It has a right child that we have not finished yet: go into the right subtree first.
- It has no right child, or we have just finished the right subtree: emit it and pop it.
The node alone cannot tell us which case we are in. The trick is to remember the last node we emitted. In postorder the root of a subtree is the last node of that subtree to be emitted, so if top.right == lastVisited, we have just come back from the right subtree.
Algorithm.
- Set
cur = root,lastVisited = null. - While
curis notnullor the stack is not empty:- Walk left from
cur, pushing every node. - Peek at
top. Iftop.rightexists and is notlastVisited, setcur = top.right. - Otherwise append
top, pop it, and setlastVisitedto it.
- Walk left from
static List<Integer> oneStack(TreeNode root) {
List<Integer> out = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root, lastVisited = null;
while (cur != null || !stack.isEmpty()) {
while (cur != null) { // go as far left as possible
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek(); // its left subtree is finished
if (top.right != null && top.right != lastVisited) {
cur = top.right; // right subtree not done yet: go there first
} else {
out.add(top.val); // both subtrees done: emit and retire it
lastVisited = stack.pop();
}
}
return out;
}
Dry run on Example 1 (1 -> right 2 -> left 3). The stack is written bottom to top.
| Step | Stack | top | Decision | Output |
|---|---|---|---|---|
| 1 | [1] | 1 | right child 2 is not lastVisited (null): go right |
|
| 2 | [1, 2, 3] | 3 | pushed 2 and 3 walking left; 3 has no right child: emit | 3 |
| 3 | [1, 2] | 2 | no right child: emit | 3 2 |
| 4 | [1] | 1 | right child 2 equals lastVisited: emit |
3 2 1 |
Without lastVisited, step 4 would send us into node 2’s subtree a second time and the loop would never end.
Complexity. Time O(n): each node is pushed once, popped once, and peeked at most twice. Space O(h) for the stack, and values are produced in final order as the walk proceeds.
Edge cases and pitfalls
- Empty tree. All three methods return an empty list. Approach 2 must check for a
nullroot becauseArrayDequerejectsnull. - Push order in Approach 2. Pushing right before left produces ordinary preorder, whose reverse is not postorder.
- Compare nodes, not values.
top.right != lastVisitedcompares references. Comparing values would break on duplicates. - Skewed trees. Both left and right chains give the values in reverse; the tests run the iterative versions on chains of
10^5nodes, where recursion would risk aStackOverflowError. - Reversing an
ArrayList. UseCollections.reverseonce at the end. Inserting at index 0 on every step isO(n)per insert andO(n^2)overall.
Related problems
- Post-order Traversal of Binary Tree using 2 stack is Approach 2 written with an explicit second stack.
- Post-order Traversal of Binary Tree using 1 stack develops Approach 3.
- Preorder Traversal and Inorder Traversal of Binary Tree are the other two depth-first orders.
- Pre, Post, Inorder in one traversal produces all three orders from one walk.
- Maximum Depth in BT is a postorder computation: a node’s depth needs both children’s depths first.
- Diameter of Binary Tree combines child results in postorder too.