โ† Back to DevBytes

Symmetric Tree: Multiple Solutions and Complexity Analysis

Symmetric Tree: Multiple Solutions and Complexity Analysis

The Symmetric Tree problem is one of the most classic and frequently asked questions in coding interviews. It tests your understanding of tree traversal, recursion, and the ability to translate a visual property (mirror symmetry) into algorithmic logic. In this tutorial, we will explore what a symmetric tree is, why it matters, multiple ways to solve it, and a detailed complexity analysis of each approach.

What Is a Symmetric Tree?

A binary tree is considered symmetric if it is a mirror of itself when drawn around its center. In other words, the left subtree must be a mirror reflection of the right subtree. This means that for every pair of corresponding nodes, the left child of one must equal the right child of the other, and vice versa.

Consider the following example of a symmetric tree:

        1
       / \
      2   2
     / \ / \
    3  4 4  3

This tree is symmetric because every node on the left side mirrors the corresponding node on the right side. Now consider this non-symmetric tree:

        1
       / \
      2   2
       \   \
       3    3

Although the values look similar, the structure is not mirrored, so the tree is not symmetric.

Why It Matters

The symmetric tree problem is more than just an interview exercise. It teaches several fundamental concepts:

These skills transfer directly to real-world scenarios such as validating data structures, comparing configurations, and implementing search algorithms.

Defining the Tree Node

Before diving into the solutions, let us define the basic tree node structure that all examples will use. We will use Python for clarity, but the logic applies to any language.

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

Each node holds a value and references to its left and right children. This simple structure is the foundation for all the algorithms we will discuss.

Solution 1: Recursive Approach

The most intuitive way to solve this problem is through recursion. The key insight is that two trees are mirrors of each other if:

Here is the implementation:

def isSymmetric(root):
    if root is None:
        return True
    return isMirror(root.left, root.right)

def isMirror(left, right):
    if left is None and right is None:
        return True
    if left is None or right is None:
        return False
    if left.val != right.val:
        return False
    return isMirror(left.left, right.right) and isMirror(left.right, right.left)

The isSymmetric function serves as an entry point. It handles the edge case of an empty tree and then delegates the comparison to the helper function isMirror. The helper function recursively checks the outer and inner pairs of nodes.

Complexity Analysis of the Recursive Approach

Time Complexity: O(n), where n is the number of nodes in the tree. In the worst case, we visit every node exactly once.

Space Complexity: O(h), where h is the height of the tree. This space is used by the recursion stack. In a balanced tree, h is O(log n), but in a completely skewed tree, h can be O(n).

Solution 2: Iterative Approach Using a Queue

While recursion is elegant, some environments have strict stack limits, and some interviewers prefer iterative solutions. We can simulate the recursive process using a queue. Instead of making recursive calls, we enqueue pairs of nodes that should be mirrors of each other and process them iteratively.

from collections import deque

def isSymmetric(root):
    if root is None:
        return True
    
    queue = deque()
    queue.append((root.left, root.right))
    
    while queue:
        left, right = queue.popleft()
        
        if left is None and right is None:
            continue
        if left is None or right is None:
            return False
        if left.val != right.val:
            return False
        
        queue.append((left.left, right.right))
        queue.append((left.right, right.left))
    
    return True

In this approach, we use a queue to store pairs of nodes that need to be compared. For each pair, we check if both are null (continue), if only one is null (return False), or if their values differ (return False). If they match, we enqueue the next two pairs: the outer children and the inner children.

Complexity Analysis of the Iterative Approach

Time Complexity: O(n). We still visit each node at most once, just as in the recursive solution.

Space Complexity: O(n) in the worst case. The queue can hold up to n/2 pairs of nodes at the widest level of the tree. This is generally more space than the recursive approach for balanced trees, but it avoids stack overflow issues.

Solution 3: Iterative Approach Using a Stack

As an alternative to the queue-based approach, we can use a stack. The logic is nearly identical, but the order of processing changes from breadth-first to depth-first. This can be useful if you want to explore one path fully before moving to the next.

def isSymmetric(root):
    if root is None:
        return True
    
    stack = []
    stack.append((root.left, root.right))
    
    while stack:
        left, right = stack.pop()
        
        if left is None and right is None:
            continue
        if left is None or right is None:
            return False
        if left.val != right.val:
            return False
        
        stack.append((left.left, right.right))
        stack.append((left.right, right.left))
    
    return True

The only difference from the queue version is that we use pop() instead of popleft(), which removes the most recently added element. This changes the traversal order but does not affect correctness.

Complexity Analysis of the Stack Approach

Time Complexity: O(n), same as the other approaches.

Space Complexity: O(h) in the average case, where h is the height of the tree. Because we process depth-first, the stack typically holds fewer elements than the queue at any given time. In the worst case of a skewed tree, this becomes O(n).

Solution 4: Level-Order Traversal and Palindrome Check

A more creative approach involves performing a level-order traversal and checking whether each level forms a palindrome. This approach is less efficient but demonstrates an interesting way to think about the problem.

from collections import deque

def isSymmetric(root):
    if root is None:
        return True
    
    queue = deque([root])
    
    while queue:
        level_size = len(queue)
        level_values = []
        
        for _ in range(level_size):
            node = queue.popleft()
            if node:
                level_values.append(node.val)
                queue.append(node.left)
                queue.append(node.right)
            else:
                level_values.append(None)
        
        if level_values != level_values[::-1]:
            return False
    
    return True

Here, we traverse the tree level by level, including null nodes in our values list. After collecting all values at a level, we check if the list is a palindrome. If any level fails this check, the tree is not symmetric.

Complexity Analysis of the Level-Order Approach

Time Complexity: O(n). We visit each node once, and the palindrome check for each level is proportional to the number of nodes at that level.

Space Complexity: O(n). We store all nodes at the widest level, which can be up to n/2 nodes. Additionally, we store the level values list, which also scales with the width of the tree.

Comparing the Solutions

Each solution has its trade-offs. The recursive approach is the most concise and is often the easiest to explain in an interview. The iterative queue approach avoids recursion limits and is a safe choice for very deep trees. The stack-based approach offers similar benefits with potentially lower memory usage for balanced trees. The level-order palindrome approach is more of a conceptual exercise and is generally not preferred for production code due to its higher constant factors.

Best Practices

When implementing a symmetric tree check in real-world code, keep the following best practices in mind:

Common Pitfalls

Even experienced developers can make mistakes with this problem. Here are some common pitfalls to watch out for:

Testing Your Implementation

To ensure your solution is correct, test it with a variety of cases. Here is a simple test harness:

def test_isSymmetric():
    # Test 1: Symmetric tree
    root1 = TreeNode(1)
    root1.left = TreeNode(2)
    root1.right = TreeNode(2)
    root1.left.left = TreeNode(3)
    root1.left.right = TreeNode(4)
    root1.right.left = TreeNode(4)
    root1.right.right = TreeNode(3)
    assert isSymmetric(root1) == True, "Test 1 failed"
    
    # Test 2: Asymmetric tree
    root2 = TreeNode(1)
    root2.left = TreeNode(2)
    root2.right = TreeNode(2)
    root2.left.right = TreeNode(3)
    root2.right.right = TreeNode(3)
    assert isSymmetric(root2) == False, "Test 2 failed"
    
    # Test 3: Single node
    root3 = TreeNode(1)
    assert isSymmetric(root3) == True, "Test 3 failed"
    
    # Test 4: Empty tree
    assert isSymmetric(None) == True, "Test 4 failed"
    
    # Test 5: Two nodes, symmetric
    root5 = TreeNode(1)
    root5.left = TreeNode(2)
    root5.right = TreeNode(2)
    assert isSymmetric(root5) == True, "Test 5 failed"
    
    print("All tests passed!")

test_isSymmetric()

Running these tests will give you confidence that your implementation handles the most important cases correctly.

Conclusion

The Symmetric Tree problem is a deceptively simple challenge that reveals a great deal about a developer's understanding of recursion, tree traversal, and algorithmic thinking. By mastering multiple solutions, from the elegant recursive approach to the robust iterative alternatives, you gain the flexibility to choose the right tool for any context. Whether you are preparing for an interview or building production code, the ability to reason about symmetry, structure, and complexity will serve you well across a wide range of tree-related problems. Practice these solutions, understand their trade-offs, and you will be well-equipped to tackle not just this problem, but the broader family of tree comparison challenges that build upon the same foundational ideas.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles