156. Binary Tree Upside Down 🔒
Problem Description
You are given a binary tree and need to transform it by flipping it upside down according to specific rules.
The transformation works as follows:
- The leftmost node (original left child) becomes the new root of the tree
- The original root node becomes the right child of what was previously its left child
- The original right child becomes the left child of what was previously the root's left child
This transformation is applied recursively throughout the tree, level by level, starting from the bottom.
For example, if you have a tree structure like:
1 / \ 2 3 / \ 4 5
After the transformation, it becomes:
4 / \ 5 2 / \ 3 1
The problem guarantees that:
- Every right node has a sibling (a left node that shares the same parent)
- No right node has any children of its own
The task is to implement a function that takes the root of the original binary tree and returns the root of the new upside-down tree. If the tree is empty or has no left child, it remains unchanged.
The recursive solution works by:
- First recursively flipping the left subtree to find the new root
- Then rearranging the pointers: making the current root the right child of its original left child, and the original right child becomes the left child of the original left child
- Setting the current node's left and right pointers to
None
to avoid cycles - Returning the new root found from the leftmost path
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this is specifically a binary tree problem, a tree is a special type of graph (an acyclic connected graph). Binary trees have nodes connected by edges, making them a graph structure.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree. We need to transform the tree structure by recursively rearranging parent-child relationships.
DFS
- Yes: Based on the flowchart, when dealing with tree problems, DFS (Depth-First Search) is the recommended approach.
Conclusion: The flowchart suggests using DFS for this binary tree transformation problem.
This aligns perfectly with the solution approach:
- The algorithm uses recursive DFS to traverse down to the leftmost node (which will become the new root)
- During the recursive return phase, it rearranges the pointers at each level
- The DFS pattern allows us to process the tree from bottom to top, ensuring proper transformation at each level
- The recursive nature of DFS makes it ideal for maintaining the relationship between parent and child nodes during the transformation
The DFS traversal ensures we:
- First reach the deepest left node (future root)
- Transform connections while backtracking up the tree
- Properly reassign left and right children at each level
Intuition
The key insight is to recognize that the leftmost path in the tree will become the "spine" of our new tree. Since the leftmost node becomes the new root, we need to traverse all the way down the left side first before we can start our transformation.
Think about it this way: if we try to transform from top to bottom, we'd lose our references to the children nodes before we could properly reconnect them. Instead, we need to work from bottom to top.
Consider what happens at each node during the transformation:
- The left child needs to "adopt" its parent as its right child
- The left child also needs to "adopt" its sibling (parent's right child) as its left child
- The parent loses both its children after being adopted
This naturally suggests a recursive approach where we:
- First recursively process the left subtree to get our new root
- Then perform the pointer reassignments when returning from recursion
The beauty of this approach is that when we're at any node, its left child has already been fully processed and transformed. So when we do root.left.right = root
and root.left.left = root.right
, we're connecting to an already-transformed subtree.
The reason we set root.left = None
and root.right = None
is crucial - without this, we'd create cycles in our tree. Once a node becomes a child in the new structure, it shouldn't maintain its old parent-child relationships.
The base case if root is None or root.left is None
handles two scenarios:
- Empty tree: nothing to transform
- Node with no left child: this node is already at the leftmost position, so it becomes the root of its subtree
This recursive DFS pattern elegantly handles the level-by-level transformation mentioned in the problem, as the recursion stack naturally processes nodes from the deepest level up to the root.
Solution Approach
The solution implements a recursive DFS approach to transform the binary tree from bottom to top. Let's walk through the implementation step by step:
Base Case Handling:
if root is None or root.left is None: return root
This handles two scenarios:
- If the tree is empty (
root is None
), returnNone
- If the current node has no left child, it means this node will be the root of its transformed subtree, so we return it as-is
Recursive Call:
new_root = self.upsideDownBinaryTree(root.left)
We recursively process the left subtree first. This call will:
- Travel all the way down the leftmost path
- Return the leftmost node as the
new_root
of the entire tree - This
new_root
remains unchanged throughout all recursive returns
Pointer Reassignment:
After the recursive call returns, we know that root.left
has been fully processed. Now we perform the transformation:
root.left.right = root
The original left child now points to its parent as its right child.
root.left.left = root.right
The original left child now points to its sibling (parent's right child) as its left child.
Cleanup:
root.left = None root.right = None
We must clear the current node's children to prevent cycles. After being "adopted" by its left child, the current node becomes a leaf in the new tree structure.
Return:
return new_root
We propagate the new root back up through all recursive calls.
Example Walkthrough: For a tree like:
1 / \ 2 3
- Call on node 1 → recursively calls on node 2
- Node 2 has no left child, so it returns itself as
new_root = 2
- Back at node 1:
root.left.right = root
→ node 2's right child becomes node 1root.left.left = root.right
→ node 2's left child becomes node 3- Clear node 1's children
- Return node 2
Final structure:
2 / \ 3 1
The algorithm has O(n)
time complexity (visiting each node once) and O(h)
space complexity where h
is the height of the tree (for the recursion stack).
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate how the solution transforms a binary tree:
Initial Tree:
1 / \ 2 3 / \ 4 5
Step-by-step Execution:
- First Call:
upsideDownBinaryTree(1)
- Node 1 has a left child (2), so we recursively call on node 2
- Second Call:
upsideDownBinaryTree(2)
- Node 2 has a left child (4), so we recursively call on node 4
- Third Call:
upsideDownBinaryTree(4)
- Node 4 has no left child (base case!)
- Return node 4 as the new root
- Back to Second Call (node 2):
new_root = 4
(from the recursive call)- Now perform transformations:
2.left.right = 2
→ Node 4's right child becomes node 22.left.left = 2.right
→ Node 4's left child becomes node 5- Set
2.left = None
and2.right = None
- Tree now looks like:
4 / \ 5 2
- Return
new_root = 4
- Back to First Call (node 1):
new_root = 4
(propagated from recursive call)- Now perform transformations:
1.left.right = 1
→ Node 2's right child becomes node 11.left.left = 1.right
→ Node 2's left child becomes node 3- Set
1.left = None
and1.right = None
- Final tree structure:
4 / \ 5 2 / \ 3 1
- Return
new_root = 4
Key Observations:
- The leftmost node (4) becomes the new root and never changes
- Each node "adopts" its parent as its right child and its sibling as its left child
- The transformations happen bottom-up, ensuring we don't lose references
- Original parent nodes become leaves in the new structure
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional
9
10
11class Solution:
12 def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
13 """
14 Flips a binary tree upside down and returns the new root.
15
16 The transformation follows these rules:
17 - The original left child becomes the new root
18 - The original root becomes the right child of the new root
19 - The original right child becomes the left child of the new root
20
21 Args:
22 root: The root node of the binary tree to flip
23
24 Returns:
25 The new root of the flipped binary tree
26 """
27 # Base case: if root is None or root has no left child, return root as is
28 if root is None or root.left is None:
29 return root
30
31 # Recursively flip the left subtree and get the new root
32 # The leftmost node will eventually become the new root of entire tree
33 new_root = self.upsideDownBinaryTree(root.left)
34
35 # Rearrange the connections:
36 # The original left child's right pointer now points to the original root
37 root.left.right = root
38 # The original left child's left pointer now points to the original right child
39 root.left.left = root.right
40
41 # Clear the original root's pointers as it's now a leaf node
42 root.left = None
43 root.right = None
44
45 # Return the new root (which propagates up from the leftmost node)
46 return new_root
47
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Flips a binary tree upside down following specific rules:
19 * - The original left child becomes the new root
20 * - The original root becomes the new right child
21 * - The original right child becomes the new left child
22 * This transformation is applied recursively from bottom to top
23 *
24 * @param root The root of the binary tree to flip
25 * @return The new root of the flipped binary tree
26 */
27 public TreeNode upsideDownBinaryTree(TreeNode root) {
28 // Base case: if tree is empty or has no left child, no flip needed
29 if (root == null || root.left == null) {
30 return root;
31 }
32
33 // Recursively flip the left subtree and get the new root
34 // The leftmost node will eventually become the new root of entire tree
35 TreeNode newRoot = upsideDownBinaryTree(root.left);
36
37 // Perform the flip transformation:
38 // 1. Original root becomes the right child of its original left child
39 root.left.right = root;
40
41 // 2. Original right child becomes the left child of the original left child
42 root.left.left = root.right;
43
44 // 3. Clear the original root's children to avoid cycles
45 root.left = null;
46 root.right = null;
47
48 // Return the new root (which bubbles up from the leftmost node)
49 return newRoot;
50 }
51}
52
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Flips a binary tree upside down.
16 * The transformation follows these rules:
17 * - The original left child becomes the new root
18 * - The original root becomes the new right child
19 * - The original right child becomes the new left child
20 * This process is applied recursively from bottom to top.
21 *
22 * @param root The root of the binary tree to flip
23 * @return The new root of the flipped tree
24 */
25 TreeNode* upsideDownBinaryTree(TreeNode* root) {
26 // Base case: if root is null or has no left child, return as is
27 if (root == nullptr || root->left == nullptr) {
28 return root;
29 }
30
31 // Recursively flip the left subtree and get the new root
32 TreeNode* new_root = upsideDownBinaryTree(root->left);
33
34 // Perform the flip transformation:
35 // The original left child's right pointer now points to the original root
36 root->left->right = root;
37 // The original left child's left pointer now points to the original right child
38 root->left->left = root->right;
39
40 // Clear the original root's pointers as it's now a leaf node
41 root->left = nullptr;
42 root->right = nullptr;
43
44 // Return the new root of the flipped tree
45 return new_root;
46 }
47};
48
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8
9 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
10 this.val = (val === undefined ? 0 : val);
11 this.left = (left === undefined ? null : left);
12 this.right = (right === undefined ? null : right);
13 }
14}
15
16/**
17 * Flips a binary tree upside down.
18 * The transformation follows these rules:
19 * - The original left child becomes the new root
20 * - The original root becomes the new right child
21 * - The original right child becomes the new left child
22 * This process is applied recursively from bottom to top.
23 *
24 * @param root - The root of the binary tree to flip
25 * @returns The new root of the flipped tree
26 */
27function upsideDownBinaryTree(root: TreeNode | null): TreeNode | null {
28 // Base case: if root is null or has no left child, return as is
29 if (root === null || root.left === null) {
30 return root;
31 }
32
33 // Recursively flip the left subtree and get the new root
34 const newRoot: TreeNode | null = upsideDownBinaryTree(root.left);
35
36 // Perform the flip transformation:
37 // The original left child's right pointer now points to the original root
38 root.left.right = root;
39 // The original left child's left pointer now points to the original right child
40 root.left.left = root.right;
41
42 // Clear the original root's pointers as it's now a leaf node
43 root.left = null;
44 root.right = null;
45
46 // Return the new root of the flipped tree
47 return newRoot;
48}
49
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree.
The algorithm visits each node exactly once during the recursive traversal. At each node, it performs constant time operations:
- Checking if the node or its left child is null:
O(1)
- Making the recursive call on the left child
- Reassigning pointers (root.left.right, root.left.left, root.left, root.right):
O(1)
Since we visit all n
nodes once and perform O(1)
operations at each node, the total time complexity is O(n)
.
Space Complexity: O(h)
where h
is the height of the binary tree.
The space complexity is determined by the recursive call stack. In the worst case:
- For a skewed tree (essentially a linked list), the height
h = n
, giving usO(n)
space complexity - For a balanced tree, the height
h = log(n)
, giving usO(log n)
space complexity
The algorithm doesn't use any additional data structures besides the implicit call stack for recursion. Each recursive call adds one frame to the stack, and the maximum depth of recursion equals the height of the tree.
Therefore, the space complexity is O(h)
, which ranges from O(log n)
in the best case (balanced tree) to O(n)
in the worst case (skewed tree).
Common Pitfalls
1. Forgetting to Clear Original Pointers
One of the most common mistakes is forgetting to set root.left = None
and root.right = None
after the pointer reassignment. This creates a cycle in the tree structure.
Incorrect Code:
def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None or root.left is None:
return root
new_root = self.upsideDownBinaryTree(root.left)
root.left.right = root
root.left.left = root.right
# Missing: root.left = None, root.right = None
return new_root
Problem: This creates a bidirectional reference where root
still points to root.left
, while root.left
also points back to root
as its right child, forming a cycle.
Solution: Always clear the original node's pointers after reassignment:
root.left = None root.right = None
2. Incorrect Order of Pointer Reassignment
Another pitfall is reassigning pointers in the wrong order or clearing them before using them.
Incorrect Code:
def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None or root.left is None:
return root
new_root = self.upsideDownBinaryTree(root.left)
temp_left = root.left # Save reference
root.left = None # Clear too early!
temp_left.right = root # Now using saved reference
temp_left.left = root.right
root.right = None
return new_root
Problem: While this might work, it's unnecessarily complex and error-prone. Clearing pointers before all reassignments are complete requires extra variables.
Solution: Complete all reassignments first, then clear:
root.left.right = root root.left.left = root.right root.left = None root.right = None
3. Misunderstanding the Base Case
Some might think the base case should only check for root is None
, missing the importance of checking root.left is None
.
Incorrect Code:
def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if root is None:
return root
# Missing check for root.left is None
new_root = self.upsideDownBinaryTree(root.left) # This will fail if root.left is None
# ... rest of the code
Problem: When root.left
is None
, the recursive call will return None
, and then root.left.right = root
will throw an AttributeError.
Solution: Always check both conditions:
if root is None or root.left is None: return root
4. Attempting an Iterative Solution Without Proper State Management
When trying to convert this to an iterative solution, a common mistake is not properly tracking all necessary pointers.
Incorrect Iterative Attempt:
def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
if not root or not root.left:
return root
current = root
while current.left:
next_node = current.left
current.left.right = current # Problem: modifying while traversing
current = next_node
return current
Problem: This modifies the tree structure while traversing, losing references needed for proper transformation.
Solution: Track previous nodes and handle transformations carefully:
def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
prev = None
current = root
next_node = None
prev_right = None
while current:
next_node = current.left
current.left = prev_right
prev_right = current.right
current.right = prev
prev = current
current = next_node
return prev
Which of the following is a good use case for backtracking?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!