← Back to DevBytes

Solving Invert Binary Tree in Python: Step-by-Step Guide

Introduction to Inverting a Binary Tree

Inverting a binary tree is one of the most famous problems in computer science, partly because of a viral anecdote involving a former Google engineer who was asked this question in an interview. Despite its reputation as a "tricky" problem, the concept is surprisingly elegant once you understand the underlying mechanics. In this tutorial, we will walk through everything you need to know about inverting a binary tree in Python, from the basic definition to multiple solution approaches, complete with working code examples.

What Is a Binary Tree?

Before we can invert a binary tree, we need to understand what a binary tree is. A binary tree is a hierarchical data structure in which each node has at most two children, referred to as the left child and the right child. Binary trees are used in many applications, including search algorithms, expression parsing, and hierarchical data representation.

Here is how we typically define a binary tree node in Python:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

This simple class gives us a node with a value and pointers to its left and right children. We will use this definition throughout the tutorial.

What Does It Mean to Invert a Binary Tree?

Inverting a binary tree means swapping the left and right children of every node in the tree. The result is a mirror image of the original tree. For example, consider the following binary tree:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

After inversion, the tree becomes:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

Notice that every node's left and right children have been swapped. The root node remains the same, but its children are reversed, and this reversal propagates down through every level of the tree.

Why Inverting a Binary Tree Matters

You might wonder why this seemingly simple operation is so important. There are several reasons why inverting a binary tree is a meaningful exercise for developers.

Foundational Recursive Thinking

Inverting a binary tree is a perfect example of a problem that naturally lends itself to recursion. The same operation — swapping left and right children — must be applied to every node in the tree. This makes it an excellent way to practice recursive thinking, which is a critical skill for solving more complex tree and graph problems.

Common Interview Question

Whether fair or not, inverting a binary tree is a staple of technical interviews at major tech companies. Interviewers use it to assess a candidate's understanding of tree traversal, recursion, and sometimes iterative approaches using stacks or queues. Being able to solve this problem confidently can set you apart in an interview setting.

Real-World Applications

While you may not invert binary trees daily in production code, the underlying concepts have real-world applications. Mirror operations are used in computer graphics for flipping images, in data structure transformations, and in algorithms that require symmetric processing. Understanding how to manipulate tree structures is also essential for working with more complex data structures like AVL trees and red-black trees.

Step-by-Step Approach to Solving the Problem

Now let us break down how to actually solve this problem. We will start with the recursive approach, which is the most intuitive, and then explore an iterative approach using breadth-first search.

Step 1: Understand the Base Case

In any recursive solution, the first thing to identify is the base case. The base case is the condition under which the recursion stops. For inverting a binary tree, the base case is when the current node is None. If there is no node, there is nothing to invert, so we simply return None.

Step 2: Swap the Children

For each node that is not None, we swap its left and right children. In Python, this can be done with a simple tuple unpacking assignment:

node.left, node.right = node.right, node.left

This single line swaps the left and right pointers of the node in place.

Step 3: Recurse on the Children

After swapping the children of the current node, we need to recursively invert the left and right subtrees. This ensures that every node in the tree gets its children swapped, not just the root.

The Recursive Solution

Putting the steps together, here is the complete recursive solution:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTree(root):
    # Base case: if the tree is empty, return None
    if root is None:
        return None
    
    # Swap the left and right children
    root.left, root.right = root.right, root.left
    
    # Recursively invert the left and right subtrees
    invertTree(root.left)
    invertTree(root.right)
    
    # Return the root of the inverted tree
    return root

Let us test this solution with the example tree from earlier:

# Build the example tree
#      4
#    /   \
#   2     7
#  / \   / \
# 1   3 6   9

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)

# Invert the tree
inverted = invertTree(root)

# Print the inverted tree using level-order traversal
from collections import deque

def print_tree(node):
    if not node:
        return
    queue = deque([node])
    result = []
    while queue:
        current = queue.popleft()
        result.append(current.val)
        if current.left:
            queue.append(current.left)
        if current.right:
            queue.append(current.right)
    print(result)

print_tree(inverted)
# Output: [4, 7, 2, 9, 6, 3, 1]

The output confirms that the tree has been successfully inverted. The root remains 4, but its children are now 7 and 2 (swapped), and this pattern continues down the tree.

Time and Space Complexity of the Recursive Solution

The time complexity of the recursive solution is O(n), where n is the number of nodes in the tree. This is because we visit every node exactly once. The space complexity is O(h), where h is the height of the tree, due to the recursion stack. In the worst case (a completely unbalanced tree), the height is n, making the space complexity O(n). In the best case (a perfectly balanced tree), the height is log(n), making the space complexity O(log n).

The Iterative Solution Using BFS

While the recursive solution is elegant, some interviewers may ask for an iterative approach. Iterative solutions avoid the overhead of the recursion stack and can be more memory-efficient for very deep trees. We can solve this problem iteratively using a breadth-first search (BFS) approach with a queue.

How the Iterative Approach Works

The idea is to traverse the tree level by level. For each node we encounter, we swap its left and right children, then add the children to the queue for processing. This continues until the queue is empty, meaning we have processed every node in the tree.

from collections import deque

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTreeIterative(root):
    if root is None:
        return None
    
    queue = deque([root])
    
    while queue:
        current = queue.popleft()
        
        # Swap the left and right children
        current.left, current.right = current.right, current.left
        
        # Add children to the queue if they exist
        if current.left:
            queue.append(current.left)
        if current.right:
            queue.append(current.right)
    
    return root

Let us test this iterative solution with the same example tree:

# Build the example tree
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)

# Invert the tree iteratively
inverted = invertTreeIterative(root)

# Print the inverted tree
def print_tree(node):
    if not node:
        return
    queue = deque([node])
    result = []
    while queue:
        current = queue.popleft()
        result.append(current.val)
        if current.left:
            queue.append(current.left)
        if current.right:
            queue.append(current.right)
    print(result)

print_tree(inverted)
# Output: [4, 7, 2, 9, 6, 3, 1]

The output is identical to the recursive solution, confirming that both approaches produce the same result.

Time and Space Complexity of the Iterative Solution

The time complexity is still O(n) because we visit every node once. The space complexity is O(w), where w is the maximum width of the tree. In the worst case (a perfectly balanced tree), the maximum width is approximately n/2, so the space complexity is O(n). This is because the queue can hold up to half the nodes at the widest level.

The Iterative Solution Using DFS with a Stack

As an alternative to BFS, we can also use a depth-first search (DFS) approach with a stack. This mimics the behavior of the recursive solution but uses an explicit stack instead of the call stack.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTreeDFS(root):
    if root is None:
        return None
    
    stack = [root]
    
    while stack:
        current = stack.pop()
        
        # Swap the left and right children
        current.left, current.right = current.right, current.left
        
        # Push children onto the stack if they exist
        if current.left:
            stack.append(current.left)
        if current.right:
            stack.append(current.right)
    
    return root

This approach has the same time and space complexity as the recursive solution: O(n) time and O(h) space, where h is the height of the tree. The stack will hold at most h nodes at any given time, which corresponds to the depth of the tree.

Comparing the Three Approaches

Now that we have explored three different solutions, let us compare them to understand when to use each one.

Recursive Approach

Iterative BFS Approach

Iterative DFS Approach

Best Practices for Working with Binary Trees in Python

Beyond just solving the invert binary tree problem, there are several best practices you should keep in mind when working with binary trees in Python.

Always Handle the Empty Tree Case

One of the most common mistakes when working with trees is forgetting to handle the case where the root is None. Always include a check for an empty tree at the beginning of your function. This serves as both a base case for recursion and a guard against null pointer errors.

Use Meaningful Variable Names

While root, left, and right are standard, make sure your helper variables are descriptive. For example, use current instead of node when iterating, and use queue or stack to clearly indicate the data structure being used.

Test with Edge Cases

Always test your solution with a variety of edge cases. Here are some important ones to consider:

# Edge case 1: Empty tree
assert invertTree(None) is None

# Edge case 2: Single node tree
single = TreeNode(1)
result = invertTree(single)
assert result.val == 1
assert result.left is None
assert result.right is None

# Edge case 3: Tree with only left children
left_only = TreeNode(1)
left_only.left = TreeNode(2)
left_only.left.left = TreeNode(3)
result = invertTree(left_only)
assert result.val == 1
assert result.right.val == 2
assert result.right.right.val == 3
assert result.left is None

# Edge case 4: Complete balanced tree
balanced = TreeNode(1)
balanced.left = TreeNode(2)
balanced.right = TreeNode(3)
result = invertTree(balanced)
assert result.left.val == 3
assert result.right.val == 2

print("All edge cases passed!")

Consider Using Type Hints

Python type hints can make your code more readable and help catch errors early. Here is the recursive solution with type hints added:

from typing import Optional

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTree(root: Optional[TreeNode]) -> Optional[TreeNode]:
    if root is None:
        return None
    
    root.left, root.right = root.right, root.left
    invertTree(root.left)
    invertTree(root.right)
    
    return root

Type hints make it clear that the function accepts an optional TreeNode and returns an optional TreeNode, improving code documentation and enabling better IDE support.

Be Aware of Python's Recursion Limit

Python has a default recursion limit of 1000, which you can check using sys.getrecursionlimit(). If you are working with very deep trees, you may need to either increase the recursion limit using sys.setrecursionlimit() or switch to an iterative approach. Increasing the recursion limit can be risky, as it may lead to a crash if the stack grows too large. The iterative approach is generally safer for production code dealing with potentially deep trees.

Common Mistakes to Avoid

When solving the invert binary tree problem, there are several common mistakes that developers make. Being aware of these can save you time and frustration.

Forgetting to Return the Root

One subtle mistake is forgetting to return the root node at the end of the function. While the inversion happens in place, returning the root is important because it allows the caller to chain operations and makes the function's behavior explicit. Without the return statement, the function returns None by default, which can cause errors in the calling code.

Swapping Before Recursing vs. Recursing Before Swapping

Interestingly, the order of swapping and recursing does not matter for this problem. Whether you swap first and then recurse, or recurse first and then swap, the result is the same. This is because the swap operation and the recursive calls operate on different levels of the tree. However, for clarity, it is generally better to swap first, as it makes the logic easier to follow.

Not Handling the Case Where Only One Child Exists

When a node has only one child, the swap still works correctly — the existing child moves to the other side, and the None pointer moves to where the child was. However, it is important to make sure your code handles this case without errors. Both the recursive and iterative solutions we wrote handle this correctly because swapping with None is a valid operation in Python.

Extending the Solution: In-Place vs. New Tree

All the solutions we have discussed so far modify the tree in place. But what if you want to create a new inverted tree without modifying the original? This can be useful when you need to preserve the original tree for other operations.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


def invertTreeCopy(root):
    if root is None:
        return None
    
    # Create a new node with the same value
    new_node = TreeNode(root.val)
    
    # Recursively invert and assign children (note the swap)
    new_node.left = invertTreeCopy(root.right)
    new_node.right = invertTreeCopy(root.left)
    
    return new_node

In this version, instead of swapping pointers on the existing nodes, we create new nodes and assign the inverted children directly. The left child of the new node is the inverted copy of the original right subtree, and vice versa. This preserves the original tree while producing a fully inverted copy.

Conclusion

Inverting a binary tree is a deceptively simple problem that teaches fundamental concepts in tree traversal, recursion, and iterative algorithms. We explored three different approaches — recursive, iterative BFS, and iterative DFS — each with its own trade-offs in terms of readability, memory usage, and suitability for different tree structures. The recursive solution is the most elegant and is perfectly suitable for most practical scenarios, while the iterative solutions provide alternatives when recursion depth is a concern. By understanding the mechanics of swapping children, handling base cases, and testing with edge cases, you now have a solid foundation not only for solving this specific problem but also for tackling more complex tree manipulation challenges. Remember that the key to mastering binary tree problems is practice — try implementing these solutions yourself, experiment with different tree structures, and always consider the time and space complexity of your code. With these skills in your toolkit, you will be well-prepared for both technical interviews and real-world programming tasks involving tree data structures.

— Ad —

Google AdSense will appear here after approval

← Back to all articles