LeetCode 94 - Binary Tree Inorder Traversal
This problem asks us to perform an inorder traversal on a binary tree and return the values of the visited nodes in the correct order. A binary tree is a hierarchical data structure where each node can have at most two children, a left child and a right child.
Difficulty: 🟢 Easy
Topics: Stack, Tree, Depth-First Search, Binary Tree
Solution
Binary Tree Inorder Traversal
Problem Understanding
This problem asks us to perform an inorder traversal on a binary tree and return the values of the visited nodes in the correct order.
A binary tree is a hierarchical data structure where each node can have at most two children, a left child and a right child. The input root represents the root node of that tree. If the tree is empty, root will be None in Python or nil in Go.
An inorder traversal follows a very specific visiting order:
- Visit the left subtree
- Visit the current node
- Visit the right subtree
This ordering is important because it determines the final output sequence.
For example, consider this tree:
1
\
2
/
3
The inorder traversal works like this:
- Start at node
1 - Visit its left subtree, which is empty
- Visit node
1 - Move to node
2 - Visit node
2's left subtree - Visit node
3 - Return to node
2 - Visit node
2
Final result:
[1, 3, 2]
The constraints tell us that:
- The tree can contain between
0and100nodes - Node values range from
-100to100
Since the tree is relatively small, performance is not extremely demanding, but we should still aim for the optimal traversal solution.
Some important edge cases include:
- An empty tree
- A tree with only one node
- A completely left-skewed tree
- A completely right-skewed tree
- Trees where some children are missing
A naive implementation can easily fail if it does not properly handle None or nil child pointers.
Approaches
Brute Force Approach
The most straightforward solution is recursive depth-first traversal.
The recursive algorithm directly mirrors the definition of inorder traversal:
- Recursively traverse the left subtree
- Record the current node's value
- Recursively traverse the right subtree
This works because recursion naturally keeps track of where we came from in the tree. Each recursive call pauses execution until its subtree is fully processed.
Although this solution is already efficient enough for the problem constraints, recursion implicitly uses the call stack. The follow up asks whether we can solve the problem iteratively, which means we should explicitly manage traversal state ourselves.
Optimal Approach
The optimal iterative solution uses an explicit stack to simulate recursion.
The key insight is that inorder traversal requires us to fully process all left descendants before visiting a node. A stack is perfect for this because it allows us to remember the path back up the tree while we continue exploring left children.
The algorithm repeatedly:
- Pushes nodes while moving left
- Processes the top node once no more left children exist
- Moves to the right subtree afterward
This guarantees nodes are visited in exact inorder sequence.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force Recursive DFS | O(n) | O(h) | Uses recursive call stack |
| Optimal Iterative Stack | O(n) | O(h) | Uses explicit stack, avoids recursion |
Here:
nis the number of nodeshis the height of the tree
Algorithm Walkthrough
Iterative Inorder Traversal Using a Stack
- Initialize an empty stack and an empty result list.
The stack will help us simulate recursion. The result list stores nodes in inorder sequence.
2. Set a pointer called current to the root node.
This pointer tracks the node we are currently exploring. 3. Continue looping while either:
currentis notNone, or- the stack is not empty.
This ensures we keep processing until every node has been visited. 4. Move as far left as possible.
While current is not None:
- Push
currentonto the stack - Move
currenttocurrent.left
We do this because inorder traversal must fully process left descendants first. 5. Once no more left children exist:
- Pop the top node from the stack
- Add its value to the result list
This node is now ready to be visited because its entire left subtree has already been processed. 6. Move to the popped node's right child.
Set:
current = current.right
This begins traversal of the right subtree.
7. Repeat the process until the stack is empty and current is None.
8. Return the result list.
Why it works
The stack always stores ancestors whose left subtrees are already being explored but whose values have not yet been added to the result. When a node is popped, we know every node in its left subtree has already been processed, so it is safe to visit the node itself before moving to the right subtree. This exactly matches the inorder traversal definition.
Python Solution
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
stack = []
current = root
while current or stack:
while current:
stack.append(current)
current = current.left
current = stack.pop()
result.append(current.val)
current = current.right
return result
The implementation begins by creating two data structures:
result, which stores the traversal orderstack, which simulates recursive calls
The variable current starts at the root node.
The outer while loop continues as long as there are nodes left to process, either through the current pointer or nodes stored in the stack.
The inner loop repeatedly moves left while pushing nodes onto the stack. This preserves the path back to parent nodes.
Once no more left children exist, the algorithm pops the most recent node from the stack. Since its left subtree is complete, its value is appended to the result.
Finally, traversal shifts to the node's right subtree.
This process continues until every node has been visited exactly once.
Go Solution
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func inorderTraversal(root *TreeNode) []int {
result := []int{}
stack := []*TreeNode{}
current := root
for current != nil || len(stack) > 0 {
for current != nil {
stack = append(stack, current)
current = current.Left
}
current = stack[len(stack)-1]
stack = stack[:len(stack)-1]
result = append(result, current.Val)
current = current.Right
}
return result
}
The Go implementation follows the exact same algorithmic structure as the Python version.
Some Go-specific details include:
nilis used instead ofNone- Slices are used to implement the stack
- Popping from the stack requires manual slice resizing
- The result slice dynamically grows with
append
Since node values are small and traversal only reads data, integer overflow is not a concern here.
Worked Examples
Example 1
Input:
root = [1,null,2,3]
Tree:
1
\
2
/
3
| Step | Current | Stack | Result | Action |
|---|---|---|---|---|
| 1 | 1 | [] | [] | Push 1 |
| 2 | nil | [1] | [] | No left child |
| 3 | 1 | [] | [1] | Pop and visit 1 |
| 4 | 2 | [] | [1] | Move right |
| 5 | 2 | [] | [1] | Push 2 |
| 6 | 3 | [2] | [1] | Move left |
| 7 | 3 | [2] | [1] | Push 3 |
| 8 | nil | [2,3] | [1] | No left child |
| 9 | 3 | [2] | [1,3] | Pop and visit 3 |
| 10 | nil | [2] | [1,3] | Move right |
| 11 | 2 | [] | [1,3,2] | Pop and visit 2 |
Final result:
[1,3,2]
Example 2
Input:
root = [1,2,3,4,5,null,8,null,null,6,7,9]
Tree:
1
/ \
2 3
/ \ \
4 5 8
/ \ /
6 7 9
Traversal order:
4 -> 2 -> 6 -> 5 -> 7 -> 1 -> 3 -> 9 -> 8
Final output:
[4,2,6,5,7,1,3,9,8]
The algorithm repeatedly descends left, processes nodes, and explores right subtrees in proper inorder sequence.
Example 3
Input:
root = []
Initial state:
| Current | Stack | Result |
|---|---|---|
| nil | [] | [] |
The loop never executes because both conditions are false.
Final result:
[]
Example 4
Input:
root = [1]
| Step | Current | Stack | Result | Action |
|---|---|---|---|---|
| 1 | 1 | [] | [] | Push 1 |
| 2 | nil | [1] | [] | No left child |
| 3 | 1 | [] | [1] | Pop and visit 1 |
Final result:
[1]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Every node is pushed and popped exactly once |
| Space | O(h) | Stack stores at most the tree height |
The time complexity is linear because each node participates in a constant number of operations.
The space complexity depends on the tree height. In the worst case, a completely skewed tree behaves like a linked list, causing the stack to store all n nodes. In a balanced tree, the height is approximately log n.
Test Cases
# Definition for testing
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def inorderTraversal(self, root):
result = []
stack = []
current = root
while current or stack:
while current:
stack.append(current)
current = current.left
current = stack.pop()
result.append(current.val)
current = current.right
return result
solution = Solution()
# Empty tree
assert solution.inorderTraversal(None) == [] # tests empty input
# Single node
root = TreeNode(1)
assert solution.inorderTraversal(root) == [1] # tests one-node tree
# Right-skewed tree
root = TreeNode(1, None, TreeNode(2, TreeNode(3)))
assert solution.inorderTraversal(root) == [1, 3, 2] # tests mixed structure
# Balanced tree
root = TreeNode(
1,
TreeNode(
2,
TreeNode(4),
TreeNode(5, TreeNode(6), TreeNode(7))
),
TreeNode(
3,
None,
TreeNode(8, TreeNode(9))
)
)
assert solution.inorderTraversal(root) == [4, 2, 6, 5, 7, 1, 3, 9, 8] # tests complex tree
# Left-skewed tree
root = TreeNode(4,
TreeNode(3,
TreeNode(2,
TreeNode(1)
)
)
)
assert solution.inorderTraversal(root) == [1, 2, 3, 4] # tests deep left traversal
# Tree with negative values
root = TreeNode(0,
TreeNode(-3),
TreeNode(5)
)
assert solution.inorderTraversal(root) == [-3, 0, 5] # tests negative values
# Tree with missing children
root = TreeNode(1,
TreeNode(2, None, TreeNode(4)),
TreeNode(3)
)
assert solution.inorderTraversal(root) == [2, 4, 1, 3] # tests sparse tree
| Test | Why |
|---|---|
| Empty tree | Verifies algorithm handles no nodes correctly |
| Single node | Confirms simplest non-empty tree |
| Right-skewed tree | Tests traversal when right children dominate |
| Balanced tree | Validates normal multi-level traversal |
| Left-skewed tree | Tests deep stack growth |
| Negative values | Ensures values themselves do not affect traversal |
| Missing children | Confirms sparse trees are handled correctly |
Edge Cases
Empty Tree
An empty tree is represented by root = None in Python or nil in Go. This case is easy to mishandle if the algorithm assumes a valid node exists before entering the loop.
The implementation handles this correctly because the outer loop condition:
while current or stack
fails immediately when both are empty, returning an empty list.
Completely Left-Skewed Tree
A tree where every node only has a left child can expose issues with stack handling or recursive depth assumptions.
For example:
4
/
3
/
2
/
1
The iterative solution correctly pushes all nodes while descending left, then processes them in reverse order as they are popped from the stack.
Nodes With Missing Children
Binary trees are often sparse, meaning some nodes may have only one child.
Example:
1
/
2
\
4
Incorrect implementations sometimes assume both children exist or attempt invalid accesses.
This solution safely checks whether current is None before accessing child pointers, ensuring sparse trees work correctly without runtime errors.