Topics › Binary Trees › Hard Problems
Morris Inorder Traversal of a Binary Tree
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Return the inorder traversal of a binary tree (left subtree, then node, then right subtree) as a list of values, using only O(1) extra space apart from the output list. No recursion and no explicit stack. The tree may be changed temporarily but must be restored before the method returns.
Constraints (LeetCode 94): up to 100 nodes, values in [-100, 100]. The space restriction is the real requirement; the Java file also tests a 100000-node tree.
Example 1
1
/ \
2 3
/ \ \
4 5 6
answer = [4, 2, 5, 1, 3, 6]
Example 2
root = [1, null, 2, 3] // 1 has a right child 2, and 2 has a left child 3
answer = [1, 3, 2]
Approach 1: Recursion
Intuition. Inorder is defined recursively: traverse the left subtree, record the node, traverse the right subtree. The call stack keeps track of which node to record once its left subtree is finished.
static List<Integer> recursive(TreeNode root) {
List<Integer> out = new ArrayList<>();
inorder(root, out);
return out;
}
static void inorder(TreeNode node, List<Integer> out) {
if (node == null) return;
inorder(node.left, out);
out.add(node.val);
inorder(node.right, out);
}
Complexity. Time O(n). Space O(h) for the call stack, which is O(n) for a skewed tree.
Approach 2: Explicit stack
Intuition. Simulate the recursion. Push nodes while walking as far left as possible; the top of the stack is then the next node in inorder. Pop it, record it, and repeat the process from its right child.
static List<Integer> iterativeStack(TreeNode root) {
List<Integer> out = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) { // walk as far left as possible
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
out.add(cur.val);
cur = cur.right;
}
return out;
}
Complexity. Time O(n): every node is pushed and popped once. Space O(h) for the stack.
Approach 3: Morris traversal
Intuition. The stack exists so that, after finishing the left subtree of cur, we can get back to cur, record it, and continue to the right. Notice which node finishes the left subtree: the rightmost node of cur.left, which is cur’s inorder predecessor pred. Its right pointer is null, so it is free storage.
Morris sets pred.right = cur before descending into the left subtree. This temporary pointer is a thread. When the walk of the left subtree reaches pred and has no real right child to go to, it follows the thread back to cur. Arriving at cur again, the predecessor search finds pred.right == cur, which means “the left subtree is done”: record cur, remove the thread and go right.
- First arrival (
pred.right == null): create the thread, go left. Nothing is recorded yet, because in inorder the left subtree comes first. - Second arrival (
pred.right == cur): remove the thread, recordcur, go right. - No left child: there is nothing to come back from, so record
curand go right (to a real child or along a thread).
Algorithm.
cur = root. Whilecur != null:- If
cur.left == null: recordcur, setcur = cur.right. - Otherwise find
pred: start atcur.leftand move right whilepred.rightis neithernullnorcur.- If
pred.right == null: setpred.right = cur, thencur = cur.left. - Else: set
pred.right = null, recordcur, thencur = cur.right.
- If
static List<Integer> morris(TreeNode root) {
List<Integer> out = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
out.add(cur.val);
cur = cur.right;
} else {
TreeNode pred = cur.left; // inorder predecessor: rightmost node of the left subtree
while (pred.right != null && pred.right != cur) pred = pred.right;
if (pred.right == null) { // first arrival: leave a thread back, go left
pred.right = cur;
cur = cur.left;
} else { // second arrival via the thread: visit, remove it
pred.right = null;
out.add(cur.val);
cur = cur.right;
}
}
}
return out;
}
Dry run on Example 1:
| cur | left child? | pred | pred.right was | action | output so far |
|---|---|---|---|---|---|
| 1 | yes | 5 | null | thread 5→1, go left | |
| 2 | yes | 4 | null | thread 4→2, go left | |
| 4 | no | record 4, follow thread to 2 | 4 | ||
| 2 | yes | 4 | 2 | remove thread, record 2, go right | 4 2 |
| 5 | no | record 5, follow thread to 1 | 4 2 5 | ||
| 1 | yes | 5 | 1 | remove thread, record 1, go right | 4 2 5 1 |
| 3 | no | record 3, go right | 4 2 5 1 3 | ||
| 6 | no | record 6, go right (null) | 4 2 5 1 3 6 |
Every thread that was created was also removed, so the tree ends in its original shape.
Complexity. Time O(n). Each predecessor search walks the right spine of one node’s left subtree, and each spine is walked exactly twice: once to create the thread, once to remove it. Those spines are disjoint, so the total work is proportional to the number of edges. Space O(1) beyond the output.
Why Morris inorder is worth knowing
Inorder of a binary search tree is its values in sorted order, so Morris inorder is the standard way to iterate a BST in sorted order with constant memory. That is the basis of O(1)-space solutions to “k-th smallest element in a BST” (stop after k records, but only after cleaning up any threads still in place) and “recover a BST with two swapped nodes” (look for the two places where the sorted order breaks).
Morris preorder is the same code with the record moved from the second arrival to the first.
Edge cases and pitfalls
- Empty tree. The loop does not run; the result is empty.
- Stopping condition of the predecessor search. The loop must stop on
pred.right == curas well aspred.right == null. Otherwise, on the second arrival, it would follow the thread back intocurand loop forever. - Recording in the wrong branch. Recording on the first arrival produces preorder. In inorder, only the “no left child” and “second arrival” branches record.
- Leaving threads behind. If you break out of the loop early, the tree still contains threads and is effectively corrupted (it may even contain cycles). Either finish the traversal or undo the remaining threads first. The tests compare the tree with a copy after every run.
- Concurrency. The tree is modified while the traversal runs, so it must not be read by anyone else at the same time.
Related problems
- Morris Preorder Traversal of a Binary Tree records each node on its first arrival instead.
- Inorder Traversal of Binary Tree covers the recursive version.
- Iterative Inorder Traversal of Binary Tree covers the explicit-stack version.
- Kth Smallest and Largest element in BST can use Morris inorder for constant extra space.
- Correct BST with two nodes swapped is a classic application of an inorder walk that Morris makes
O(1)in space.