THE WHY BEHIND EVERY DSA PROBLEM
SHEET: STRIVER A2Z · REV 0.1
UNDERSTOOD: 0 / 79 DRAFTED
STEP 13 · BINARY TREES · STORE THE RETURN ADDRESS IN THE TREE · MEDIUM

Morris Traversal (Inorder and Preorder)

WHAT IT SAYS

Traverse a binary tree inorder (or preorder) using O(1) extra space — no recursion stack, no explicit stack.

WHAT IT'S REALLY ASKING

"The stack exists for exactly one reason: before diving into a left subtree, you must remember how to get back. But look at where a left subtree FINISHES — at its rightmost node, whose right pointer is null and pointing at nothing. Free memory, sitting at precisely the spot where the walk will need directions home. Why carry a stack when the tree has empty pockets exactly where you need them?"

THE INSIGHT LADDER — FROM BRUTE FORCE TO OPTIMAL

Recursion, or an explicit stack

O(n) time, O(h) space — which is O(n) on a skewed tree

Inorder: recurse left, visit, recurse right — or simulate it with a stack, pushing nodes on the way down-left and popping to visit before turning right. Correct, canonical, and what you should write in 99 percent of real code.

WHERE THE WORK IS WASTED — Interrogate what each stack frame actually stores: a pointer to an ancestor you must revisit after its left subtree completes. That is all — a return address. Now notice the tree's own accounting: n nodes own 2n child pointers but only n-1 edges exist, so n+1 pointers hold null. The information the stack carries (one return address per pending ancestor) is SMALLER than the free space the structure already contains, and the free slots even sit in the right places. The stack is external storage rented for data the building has empty rooms for.

!
KEY OBSERVATION — THE UNLOCKlink

A predecessor's null right pointer is a free return address.

Where does an inorder walk go immediately after finishing a left subtree? To the subtree's root — the ancestor the stack was remembering. And which node is the LAST one visited inside that left subtree? Its rightmost node: the current node's inorder predecessor. That predecessor's right pointer is null — it is the subtree's bottom-right corner, nothing hangs there. So the walk's return address has a natural home: thread predecessor.right to point back at the current node, and the walk can find its way home with no stack at all. The loop then needs no memory beyond one cursor. At each node with a left child, walk down to its predecessor (left once, then right until the thread-or-null). Two cases, and this case split is the entire algorithm: Predecessor's right is null — first arrival. Plant the thread (predecessor.right = current), then descend left. The thread is a promise: when the left subtree finishes, it will deliver the walk back here. Predecessor's right already points at current — second arrival, meaning the thread you planted has just been used: the left subtree is COMPLETE. Remove the thread (restore the null), visit the current node, and move right. The thread's existence is itself the visited-flag: no marks, no sets, the temporary edge encodes 'left side done'. A node with no left child is visited immediately and the walk moves right — possibly along a thread, which is exactly the mechanism working. Preorder is a one-line reshuffle that is worth understanding rather than memorising: the ONLY difference from inorder is WHEN the visit happens relative to the thread. Inorder visits on the second arrival (after the left subtree); preorder visits on the FIRST arrival, at the moment of planting the thread, because preorder's contract is root-before-left-subtree. Same threads, same two cases, the emit statement moves from one branch to the other. Two honesty notes. Each edge near the right spine of every left subtree is walked at most three times (down, predecessor-search, return), so time stays O(n) — amortised, the predecessor searches sum to the tree size. And the tree is temporarily NOT a tree: while threads exist, there are cycles, so the structure is unsafe for concurrent readers until the walk completes and restores every null.

Thread, descend, detect, unthread

O(n) time — each edge traversed a constant number of times — O(1) space

cursor = root. Loop: if no left child, visit and go right. Otherwise find the predecessor (left once, right until null-or-cursor). If its right is null: thread it to cursor (for preorder, visit NOW), go left. If its right is cursor: unthread it (for inorder, visit NOW), go right. Stop when the cursor runs off the tree — every thread has been removed by its own second-arrival case.

WHAT YOU TRADED — O(1) space is bought by mutating the input mid-flight: the tree passes through inconsistent states, which forbids concurrent access, read-only inputs, and careless early exits (break out mid-walk and threads remain, corrupting the tree). Morris is the answer when memory is genuinely the binding constraint — embedded systems, or as the engine inside Recover BST — and the stack version is the answer everywhere else. The transferable lesson: before renting external memory for bookkeeping, audit the structure for slack — null pointers, sign bits, alignment padding — because bookkeeping often fits in the space the data already wastes, provided every borrowed slot is returned.
WATCH THE IDEA RUN
4261357
INORDER OUTPUT
0
threads currently borrowed 0
stack used 0 — that's the point
Recursion costs O(h) stack. A stack costs O(h). Both exist for ONE reason: to remember where to come back to. Look at the tree and find where that address could be stored for free.
step 1 / 12
THE PATTERN — SO YOU RECOGNIZE IT NEXT TIME

Borrow the Structure's Free Slots

YOU'LL SEE IT AGAIN WHEN

  • The auxiliary memory (stack, visited set) stores only return-addresses or flags — information proportional to the structure's own unused capacity (n+1 null pointers in any n-node binary tree).
  • There is a natural rendezvous: the walk's return target and a free slot coincide (the predecessor's null right points exactly where the walk resumes).
  • The borrowed slot's occupied-versus-free state can double as the visited flag, so detection and storage are the same mechanism.

SAME BLUEPRINT, DIFFERENT PROBLEM

Recover Binary Search Tree in O(1) spaceFlatten Binary Tree to Linked List (the same predecessor splice, made permanent)Linked List Cycle Detection (Floyd — different trick, same refuse-the-memory spirit)Convert BST to Sorted Doubly Linked List
The bar isn't "solved it once." It's "could rebuild it from the observation."