Flatten Binary Tree to Linked List | Leetcode 114
Welcome to our deep dive on Flatten Binary Tree to Linked List (Leetcode 114). This problem tests your ability to manipulate tree pointers in-place without allocating extra memory for new nodes. It heavily builds on your understanding of pre-order tree traversal.
Problem Statement
Given the root of a binary tree, flatten the tree into a "linked list":
- The "linked list" should use the same
TreeNodeclass where therightchild pointer points to the next node in the list and theleftchild pointer is alwaysnull. - The "linked list" should be in the same order as a pre-order traversal of the binary tree.
Note: You must flatten the tree in-place.
Example:
root = [1, 2, 5, 3, 4, null, 6]
Output: [1, null, 2, null, 3, null, 4, null, 5, null, 6]
Approach 1: Recursive Post-Order (Right, Left, Root)
If you strictly do a pre-order traversal (Root, Left, Right) and overwrite the right pointer as you go, you will accidentally overwrite and lose access to the right subtree before you get a chance to visit it!
The brilliant workaround is to traverse the tree in Reverse Post-Order (Right, Left, Root).
By tracking the previously_visited node, we can go all the way down to the bottom-rightmost node of the tree. As our recursion bubbles back up:
- We set the current node's
rightpointer to thepreviously_visitednode. - We set the current node's
leftpointer tonull. - We update
previously_visitedto be the current node!
# 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 __init__(self):
self.prev = None
def flatten(self, root: Optional[TreeNode]) -> None:
"""
Do not return anything, modify root in-place instead.
"""
if not root:
return
# Traverse Right, Left, Root (Reverse Post-Order)
self.flatten(root.right)
self.flatten(root.left)
# Wire the current node to point to the previously processed node
root.right = self.prev
root.left = None
# Move the prev pointer up to the current node
self.prev = root
Complexity Analysis
| Metric | Complexity | Explanation |
|---|---|---|
| Time | O(N) | We visit every node in the tree exactly once during the reverse post-order traversal. |
| Space | O(N) | The recursion call stack will hold up to N frames in the worst-case scenario (a degenerate tree), or O(log N) for a balanced tree. |
Approach 2: Iterative Morris Traversal (O(1) Space)
The recursive approach is elegant, but it requires O(N) stack space. Can we achieve optimal O(1) constant space? Yes, using a manipulation of Morris Traversal.
The core requirement of a pre-order flattening is that a node's entire left subtree must be processed before moving to its right subtree. Furthermore, the rightmost node of that left subtree needs to connect directly to the original right child!
Algorithm at current node:
- If
nodehas a left child, find the rightmost node deep within that left subtree. - Wire the
rightpointer of that rightmost node tonode.right(saving our right branch from being lost). - Now that the right branch is safe, overwrite
node.rightto benode.left. - Set
node.lefttonull. - Move
nodetonode.rightand repeat!
graph TD
A["Node(1)"] --> B["Left(2)"]
A --> C["Right(5)"]
B --> D["Left(3)"]
B --> E["Right(4)"]
C --> F["Right(6)"]
E -.->|"1. Wire rightmost left-child to original right"| C
A -.->|"2. Move left branch to right"| B
style E fill:#FF9800,stroke:#fff,stroke-width:2px,color:#fff
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
curr = root
while curr:
if curr.left:
# 1. Find the rightmost node of the left subtree
runner = curr.left
while runner.right:
runner = runner.right
# 2. Wire it to the current node's right child
runner.right = curr.right
# 3. Move the entire left subtree over to the right
curr.right = curr.left
curr.left = None
# 4. Step down to the next right node (which was previously our left child!)
curr = curr.right
Complexity Analysis
| Metric | Complexity | Explanation |
|---|---|---|
| Time | O(N) | While there is a nested while loop, every edge in the tree is traversed at most twice (once by curr, once by runner). This amortizes strictly to O(N). |
| Space | O(1) | We only allocate two pointer variables (curr and runner). The tree is morphed entirely in-place. |