145. Binary Tree Postorder Traversal - Explanation
Description
You are given the root of a binary tree, return the postorder traversal of its nodes' values.
Example 1:
Input: root = [1,2,3,4,5,6,7]
Output: [4,5,2,6,7,3,1]Example 2:
Input: root = [1,2,3,null,4,5,null]
Output: [4,2,5,3,1]Example 3:
Input: root = []
Output: []Constraints:
0 <= number of nodes in the tree <= 100-100 <= Node.val <= 175
Follow up: Recursive solution is trivial, could you do it iteratively?
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Tree Structure - Understanding how nodes connect via left and right children
- Recursion - Using recursive calls to naturally traverse tree structures
- Stacks - Simulating recursion iteratively by explicitly managing the call stack and visit states
1. Depth First Search
Intuition
Postorder traversal visits nodes in the order: left subtree, right subtree, current node. This is useful when we need to process children before their parent, such as when deleting nodes or evaluating expression trees. Recursion naturally handles this by processing both subtrees before adding the current node's value.
Algorithm
- Create a result list to store the node values.
- Define a recursive helper function that takes a
nodeas input. - If the
nodeisnull, return immediately (base case). - Recursively call the function on the left child.
- Recursively call the function on the right child.
- Add the current node's value to the result list.
- Return the result list after traversing the entire tree.
# 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def postorder(node):
if not node:
return
postorder(node.left)
postorder(node.right)
res.append(node.val)
postorder(root)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- space for the recursion stack.
- space for the output array.
2. Iterative Depth First Search - I
Intuition
We use a stack with a visited flag to track whether we've already processed a node's children. When we first encounter a node, we push it back with a visited flag set to true, then push its children. On the second visit (when the flag is true), we know both children have been processed, so we add the node's value to the result.
Algorithm
- Initialize a stack with the root node paired with a
falsevisited flag. - While the stack is not empty:
- Pop a node and its
visitedflag. - If the node is
null, continue. - If
visitedistrue, add the node's value to the result. - Otherwise, push the node back with
visitedset totrue, then push the right child (visitedfalse), then the left child (visitedfalse).
- Pop a node and its
- 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
visit = [False]
res = []
while stack:
cur, v = stack.pop(), visit.pop()
if cur:
if v:
res.append(cur.val)
else:
stack.append(cur)
visit.append(True)
stack.append(cur.right)
visit.append(False)
stack.append(cur.left)
visit.append(False)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- space for the stacks.
- space for the output array.
3. Iterative Depth First Search - II
Intuition
Postorder is the reverse of a modified preorder traversal. If we traverse in the order: current, right, left (instead of current, left, right), and then reverse the result, we get the postorder sequence. This avoids the complexity of tracking visited nodes.
Algorithm
- Initialize an empty result list and an empty stack.
- Set the current node to the root.
- While the current node is not
nullor the stack is not empty:- If the current node is not
null, add its value to the result, push it onto the stack, and move to the right child. - Otherwise, pop a node from the stack and move to its left child.
- If the current node is not
- Reverse the result list to get the correct postorder sequence.
- 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = []
cur = root
while cur or stack:
if cur:
res.append(cur.val)
stack.append(cur)
cur = cur.right
else:
cur = stack.pop()
cur = cur.left
res.reverse()
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- space for the stack.
- space for the output array.
4. Morris Traversal
Intuition
Similar to the iterative approach that reverses a modified preorder, Morris Traversal for postorder works by performing a reverse preorder traversal (current, right, left) without using extra space for a stack. We use temporary thread links from the leftmost node of the right subtree back to the current node. After the traversal, we reverse the result.
Algorithm
- Initialize the current node to the root.
- While the current node is not
null:- If the current node has no right child, add its value to the result and move to the left child.
- Otherwise, find the leftmost node in the right subtree.
- If the leftmost node's left pointer is
null, add the current value to the result, set the left pointer to the current node (create thread), and move to the right child. - If the left pointer already points to the current node, remove the thread and move to the left child.
- Reverse the result list to get the correct postorder sequence.
- 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
cur = root
while cur:
if not cur.right:
res.append(cur.val)
cur = cur.left
else:
prev = cur.right
while prev.left and prev.left != cur:
prev = prev.left
if not prev.left:
res.append(cur.val)
prev.left = cur
cur = cur.right
else:
prev.left = None
cur = cur.left
res.reverse()
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Common Pitfalls
Adding Node Value Before Recursing on Children
Postorder requires visiting children before the current node. Adding the value before recursive calls produces preorder traversal instead.
# Wrong: this is preorder, not postorder
res.append(node.val)
postorder(node.left)
postorder(node.right)Wrong Child Order in Iterative Approach
In the iterative approach using reversal, you must traverse current -> right -> left, then reverse. Traversing current -> left -> right and reversing gives incorrect results.
Sign in to join the discussion