Morris Traversal Inorder Without Stack in DSA Typescript - Time & Space Complexity
We want to understand how the Morris Traversal algorithm performs as the size of the tree grows.
Specifically, how many steps it takes to visit all nodes without using extra memory for a stack.
Analyze the time complexity of the following Morris Inorder Traversal code.
function morrisInorder(root: TreeNode | null): void {
let current = root;
while (current !== null) {
if (current.left === null) {
// Visit current node
current = current.right;
} else {
let predecessor = current.left;
while (predecessor.right !== null && predecessor.right !== current) {
predecessor = predecessor.right;
}
if (predecessor.right === null) {
predecessor.right = current;
current = current.left;
} else {
predecessor.right = null;
// Visit current node
current = current.right;
}
}
}
}
This code visits all nodes of a binary tree in inorder without using a stack or recursion by temporarily modifying pointers.
Look for loops and repeated steps in the code.
- Primary operation: The outer while loop runs once per node, moving through the tree.
- Inner loop: The inner while loop finds the rightmost node in the left subtree (predecessor) and can run multiple times per node.
- How many times: Each edge in the tree is visited at most twice due to temporary pointer changes.
As the number of nodes (n) increases, the algorithm visits each node and its edges a limited number of times.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 visits (nodes + edges) |
| 100 | About 200 visits |
| 1000 | About 2000 visits |
Pattern observation: The operations grow roughly linearly with the number of nodes.
Time Complexity: O(n)
This means the algorithm visits each node a constant number of times, so the total steps grow directly with the number of nodes.
[X] Wrong: "The inner loop makes this algorithm slower than linear because it can run many times per node."
[OK] Correct: Each edge is visited at most twice due to temporary links, so the total work is still proportional to the number of nodes.
Understanding Morris Traversal shows you can traverse trees efficiently without extra memory, a useful skill for optimizing space in interviews.
What if we used recursion instead of modifying pointers? How would the time and space complexity change?
Practice
Solution
Step 1: Understand space usage in tree traversals
Traditional inorder traversal uses recursion or a stack, which requires extra memory proportional to tree height.Step 2: Morris Traversal space optimization
Morris Traversal cleverly uses threaded binary trees to avoid stack or recursion, thus using O(1) extra space.Final Answer:
It uses no extra space for stack or recursion. -> Option BQuick Check:
Morris Traversal = O(1) space [OK]
- Thinking Morris is faster in time complexity
- Believing Morris only works on BSTs
- Assuming Morris modifies tree permanently
Solution
Step 1: Identify correct initialization type
We start traversal from the root node, so current should be root and allow null for empty trees.Step 2: Check TypeScript syntax
TypeScript requires correct type with possible null, so 'TreeNode | null' is correct.Final Answer:
let current: TreeNode | null = root; -> Option AQuick Check:
Correct type and initialization = let current: TreeNode | null = root; [OK]
- Assigning null without type union
- Starting from root.left instead of root
- Using uninitialized or wrong type
2 / \ 1 3What will be the printed output sequence?
Solution
Step 1: Understand inorder traversal order
Inorder traversal visits left subtree, then node, then right subtree. For this tree, order is 1, 2, 3.Step 2: Morris traversal mimics inorder without stack
Morris traversal outputs nodes in inorder sequence, so output will be [1, 2, 3].Final Answer:
[1, 2, 3] -> Option CQuick Check:
Inorder of given tree = [1, 2, 3] [OK]
- Confusing preorder or postorder with inorder
- Assuming Morris changes output order
- Mixing left and right subtree order
while (current !== null) {
if (current.left === null) {
console.log(current.val);
current = current.right;
} else {
let predecessor = current.left;
while (predecessor.right !== null && predecessor.right !== current) {
predecessor = predecessor.right;
}
if (predecessor.right === null) {
predecessor.right = current;
current = current.left;
} else {
predecessor.right = null;
// Missing console.log here
current = current.right;
}
}
}Solution
Step 1: Identify missing output in else block
When restoring the tree link, the node's value must be printed before moving right, else node is skipped.Step 2: Effect of missing print
Without printing current.val in the unthreading step, nodes with left subtrees are skipped in the output, causing incomplete traversal results.Final Answer:
Missing printing current.val before moving right after restoring link. -> Option DQuick Check:
Missing console.log(current.val) in unthreading step causes skipped nodes [OK]
- Ignoring print statement placement
- Confusing predecessor initialization
- Thinking '===' causes infinite loop
4
/ \
2 5
/ \
1 3
Using Morris inorder traversal, what is the exact sequence of node values printed, and why does this method not use extra memory?Solution
Step 1: Determine inorder traversal sequence
Inorder visits left subtree, node, right subtree. For this tree, sequence is 1, 2, 3, 4, 5.Step 2: Explain Morris traversal memory usage
Morris traversal creates temporary threaded links to predecessor nodes, avoiding stack or recursion, thus using O(1) extra space.Final Answer:
[1, 2, 3, 4, 5]; uses threaded links to avoid stack or recursion. -> Option AQuick Check:
Inorder = [1,2,3,4,5], O(1) space by threading [OK]
- Mixing traversal order
- Assuming recursion or stack used
- Believing tree is permanently changed
Ready to prove it?
Pass the test and this pattern moves unknown
