Facebook Pixel

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:

  1. First recursively flipping the left subtree to find the new root
  2. 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
  3. Setting the current node's left and right pointers to None to avoid cycles
  4. 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:

  1. First reach the deepest left node (future root)
  2. Transform connections while backtracking up the tree
  3. Properly reassign left and right children at each level
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. First recursively process the left subtree to get our new root
  2. 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), return None
  • 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
  1. Call on node 1 → recursively calls on node 2
  2. Node 2 has no left child, so it returns itself as new_root = 2
  3. Back at node 1:
    • root.left.right = root → node 2's right child becomes node 1
    • root.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 Evaluator

Example 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:

  1. First Call: upsideDownBinaryTree(1)
    • Node 1 has a left child (2), so we recursively call on node 2
  2. Second Call: upsideDownBinaryTree(2)
    • Node 2 has a left child (4), so we recursively call on node 4
  3. Third Call: upsideDownBinaryTree(4)
    • Node 4 has no left child (base case!)
    • Return node 4 as the new root
  4. 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 2
      • 2.left.left = 2.right → Node 4's left child becomes node 5
      • Set 2.left = None and 2.right = None
    • Tree now looks like:
          4
         / \
        5   2
    • Return new_root = 4
  5. 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 1
      • 1.left.left = 1.right → Node 2's left child becomes node 3
      • Set 1.left = None and 1.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 us O(n) space complexity
  • For a balanced tree, the height h = log(n), giving us O(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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More