Flatten Binary Tree to Linked List
MedPrompt
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.
Example 1:
Input: root = [1,2,5,3,4,null,6]
Output: [1,null,2,null,3,null,4,null,5,null,6]Example 2:
Input: root = []
Output: []Example 3:
Input: root = [0]
Output: [0]
Constraints:
- The number of nodes in the tree is in the range
[0, 2000]. -100 <= Node.val <= 100
Follow up: Can you flatten the tree in-place (with
O(1) extra space)?Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves performing a pre-order traversal of the binary tree and storing each visited node in an auxiliary list. After the traversal is complete, the list contains all nodes in the desired flattened order. We then iterate through this list, rearranging the left and right pointers of each node to form the linked list structure.
Algorithm
- If the
rootisnull, return immediately. - Create an empty
List<TreeNode>to store the nodes. - Perform a pre-order traversal on the tree. In the traversal, add each visited node to the list.
- Iterate through the list from the first element up to the second-to-last element.
- For each node at index
i, set itsleftpointer tonulland itsrightpointer to the node at indexi+1. - Set the
leftandrightpointers of the last node in the list tonull.
Walkthrough
The core idea is to separate the traversal from the restructuring. First, we perform a standard pre-order traversal (Root, Left, Right) on the tree. We can use a recursive helper function for this. During the traversal, every node we encounter is added to a dynamic array or list. Once the traversal is complete, this list holds all the tree nodes in the exact sequence required for the flattened list.
The second step is to iterate through this list of nodes from the beginning. For each node at index i, we set its left child to null and its right child to the node at index i+1. This process effectively links all the nodes together. The last node in the list will have its right child set to null, terminating the flattened list.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public void flatten(TreeNode root) { if (root == null) { return; } List<TreeNode> nodes = new ArrayList<>(); preorderTraversal(root, nodes); for (int i = 0; i < nodes.size() - 1; i++) { TreeNode curr = nodes.get(i); TreeNode next = nodes.get(i + 1); curr.left = null; curr.right = next; } // Set the last node's children to null TreeNode lastNode = nodes.get(nodes.size() - 1); lastNode.left = null; lastNode.right = null; } private void preorderTraversal(TreeNode node, List<TreeNode> nodes) { if (node == null) { return; } nodes.add(node); preorderTraversal(node.left, nodes); preorderTraversal(node.right, nodes); }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Very straightforward and easy to understand.
The logic directly follows the definition of the problem (get pre-order sequence, then link them).
Cons
Requires O(N) extra space for the list, which is inefficient and does not satisfy the follow-up constraint of O(1) space.
A more space-efficient approach is to use recursion in-place. By traversing the tree in a modified post-order fashion (Right -> Left -> Root), we can flatten the tree. We maintain a pointer to the previously processed node (prev). For each node, we first recursively flatten its right subtree, then its left subtree. Finally, we wire the current node to point to the head of the previously flattened part (prev) and update prev to be the current node.
Algorithm
- Initialize a global or class-level
TreeNodevariableprevtonull. This will track the previously visited node. - Define a recursive function
flatten(node). - Base Case: If
nodeisnull, return. - Recursively call
flatten(node.right). - Recursively call
flatten(node.left). - After the recursive calls, set
node.righttoprev. - Set
node.lefttonull. - Update
prevto be the currentnode. - Call the initial
flatten(root).
Walkthrough
This approach avoids using an explicit list by performing the rewiring during the recursion itself. The key insight is to process the subtrees in a specific order: Right, Left, Root. This is a variation of a post-order traversal. We use a helper variable, prev, which keeps track of the node that was processed last (which will be the head of the already-flattened part of the tree).
The recursive function works as follows:
- It first calls itself on the right child. This flattens the entire right subtree. After this call returns,
prevwill point to the head of the flattened right subtree. - Then, it calls itself on the left child. This flattens the left subtree. The tail of this flattened left subtree will be internally linked to the
prevfrom the previous step. After this call returns,prevwill point to the head of the flattened left subtree. - Finally, it processes the current node. It sets the current node's
rightpointer toprev(linking it to the rest of the flattened list) and itsleftpointer tonull. It then updatesprevto be the current node, making it the new head of the flattened list seen so far. By starting the process from the root, the entire tree is flattened in-place.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { private TreeNode prev = null; public void flatten(TreeNode root) { if (root == null) { return; } // Traverse right subtree first, then left, then process the root flatten(root.right); flatten(root.left); // Rewire the current node root.right = prev; root.left = null; // Update prev to the current node for the next step up in recursion prev = root; }}Complexity
Time
O(N)
Space
O(H)
Trade-offs
Pros
Solves the problem in-place without requiring an auxiliary list.
It's a clever recursive solution that is more space-efficient than the brute-force approach on average.
Cons
Uses the recursion call stack for space, which can be O(H) where H is the height of the tree. In the worst case of a skewed tree, this becomes O(N), failing the strict O(1) space constraint.
The most optimal solution achieves O(1) space complexity by modifying the tree in-place using an iterative approach inspired by Morris Traversal. We traverse the tree with a current pointer. If the current node has a left child, we find the rightmost node of its left subtree (the pre-order predecessor). We then link this predecessor's right pointer to the current node's right child and move the entire left subtree to become the current node's new right child. This process effectively flattens the tree without using extra space for a stack or list.
Algorithm
- Initialize a pointer
currtoroot. - Start a
whileloop that continues as long ascurris notnull. - Inside the loop, check if
curr.leftis notnull. - If it is, find the rightmost node of the left subtree. Let's call it
predecessor.- Start with
predecessor = curr.left. - While
predecessor.rightis notnull, updatepredecessor = predecessor.right.
- Start with
- Perform the rewiring:
predecessor.right = curr.rightcurr.right = curr.leftcurr.left = null
- Whether
curr.leftwas null or not, move to the next node by updatingcurr = curr.right. - The loop continues until the entire tree is processed.
Walkthrough
This iterative solution is the most space-efficient, achieving true O(1) extra space. It's based on the principles of Morris Traversal. The algorithm iterates through the tree, modifying pointers as it goes to create the flattened structure without a stack or recursion.
We use a curr pointer, starting at the root. In each step of the main loop:
- If
currhas no left child, it means we are already in the correct pre-order position relative to its children. We simply move to the right child (curr = curr.right) to continue. - If
currhas a left child, we need to insert the left subtree betweencurrandcurr.right. To do this, we first find the rightmost node of the left subtree. This node is the pre-order predecessor ofcurr's original right child. We then perform the rewiring:- Set the predecessor's
rightpointer tocurr.right. - Move the entire left subtree to
curr.right. - Set
curr.lefttonull. After this rewiring, we movecurrto its new right child (the former left child) and continue the process. The loop terminates whencurrbecomesnull.
- Set the predecessor's
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public void flatten(TreeNode root) { TreeNode curr = root; while (curr != null) { if (curr.left != null) { // Find the rightmost node in the left subtree TreeNode predecessor = curr.left; while (predecessor.right != null) { predecessor = predecessor.right; } // Rewire the pointers predecessor.right = curr.right; // Connect predecessor to curr's right child curr.right = curr.left; // Move left subtree to the right curr.left = null; // Set left child to null } // Move to the next node in the pre-order sequence curr = curr.right; } }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Achieves optimal O(1) space complexity, satisfying the follow-up.
It's an in-place, iterative solution that avoids recursion overhead.
Cons
The logic can be less intuitive to grasp compared to the recursive or list-based approaches.
The pointer manipulation is more complex.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public void flatten ( TreeNode root ) { while ( root != null ) { if ( root . left != null ) { TreeNode pre = root . left ; while ( pre . right != null ) { pre = pre . right ; } pre . right = root . right ; root . right = root . left ; root . left = null ; } root = root . right ; } } }/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {void} Do not return anything, modify root in-place instead. */ var flatten = function (root) { while (root) { if (root.left) { let pre = root.left; while (pre.right) { pre = pre.right; } pre.right = root.right; root.right = root.left; root.left = null; } root = root.right; } };/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: void flatten ( TreeNode * root ) { while ( root ) { if ( root -> left ) { TreeNode * pre = root -> left ; while ( pre -> right ) { pre = pre -> right ; } pre -> right = root -> right ; root -> right = root -> left ; root -> left = nullptr ; } root = root -> right ; } } };# 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 : # stack, pre-order interation def flatten ( self , root : TreeNode ) -> None : """ Do not return anything, modify root in-place instead. """ if not root : return stack = [ root ] prev = TreeNode ( 0 ) # dummy node while stack : current = stack . pop () if current . right : stack . append ( current . right ) if current . left : stack . append ( current . left ) prev . left = None prev . right = current prev = current ########### class Solution : # no stack def flatten ( self , root : Optional [ TreeNode ]) -> None : """ Do not return anything, modify root in-place instead. """ while root : if root . left : pre = root . left while pre . right : # start feom right pre = pre . right pre . right = root . right root . right = root . left root . left = None root = root . rightVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.