Facebook Pixel

101. Symmetric Tree

Problem Description

You are given the root of a binary tree. Your task is to determine whether this tree is symmetric - meaning it forms a mirror image when folded along its center vertical axis.

A binary tree is considered symmetric if:

  • The left and right subtrees are mirror images of each other
  • For any two corresponding nodes in the left and right subtrees:
    • They have the same value
    • The left child of one node corresponds to the right child of the other node
    • The right child of one node corresponds to the left child of the other node

For example, a tree like this would be symmetric:

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

The solution uses a recursive helper function dfs(root1, root2) that compares two nodes to check if they are mirrors of each other:

  1. If both nodes are None (represented by root1 == root2 when both are None), they are symmetric
  2. If only one is None or their values differ, they are not symmetric
  3. Otherwise, recursively check that:
    • The left child of root1 mirrors the right child of root2
    • The right child of root1 mirrors the left child of root2

The main function starts the comparison by calling dfs(root.left, root.right) to check if the left and right subtrees of the root are mirror images of each other.

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 not a traditional graph problem, a binary tree is a special type of graph (specifically, a connected acyclic graph). Trees are a subset of graphs where each node has at most one parent.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure, where we need to check if the tree is symmetric around its center.

DFS

  • Yes: We arrive at DFS (Depth-First Search) as our algorithm choice.

Conclusion: The flowchart suggests using DFS (Depth-First Search) for this symmetric tree problem.

This makes sense because:

  • We need to traverse the tree structure to compare corresponding nodes
  • DFS allows us to recursively explore each subtree and compare the left and right branches
  • The recursive nature of DFS perfectly matches the recursive structure of checking symmetry (comparing left.left with right.right and left.right with right.left)
  • We need to go deep into the tree structure to verify that all corresponding nodes match, which is exactly what DFS provides

The solution implements DFS through the recursive dfs helper function that explores both subtrees simultaneously, checking if they mirror each other at each level of recursion.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To check if a tree is symmetric, imagine folding the tree along its center vertical axis like folding a piece of paper. If the tree is symmetric, the left and right halves should perfectly overlap.

The key insight is that for a tree to be symmetric, every node on the left side must have a corresponding "mirror" node on the right side. These mirror nodes must:

  1. Have the same value
  2. Be at the same level and mirrored position

Think about what makes two subtrees mirror images of each other. If we're comparing a left subtree and a right subtree:

  • The root values must be equal
  • The left child of the left subtree should mirror the right child of the right subtree
  • The right child of the left subtree should mirror the left child of the right subtree

This naturally leads to a recursive approach where we compare pairs of nodes. Starting from the root's left and right children, we check if they're mirrors of each other by:

  • Comparing their values
  • Recursively checking if their children are properly mirrored

The beauty of this approach is that we're essentially traversing two trees simultaneously - one from the left side and one from the right side - and at each step verifying they're mirror images. If at any point we find nodes that should be mirrors but aren't (different values or one exists while the other doesn't), we know the tree isn't symmetric.

The base case occurs when we reach None nodes - two None nodes are considered symmetric since they represent the same "empty" structure on both sides.

Solution Approach

The solution implements a recursive DFS approach using a helper function dfs(root1, root2) to determine whether two binary trees are symmetric.

Implementation Details:

The main function isSymmetric initiates the comparison by calling dfs(root.left, root.right), checking if the left and right subtrees of the root are mirror images.

The dfs function follows this logic:

  1. Base Case - Both nodes are null:

    • When root1 == root2 (both are None), the trees are symmetric at this point, return True
    • This handles the leaf nodes and empty subtrees
  2. Asymmetric Cases:

    • If only one of root1 or root2 is None, the structure is different, return False
    • If both nodes exist but root1.val != root2.val, the values don't match, return False
  3. Recursive Case:

    • Check if the left subtree of root1 mirrors the right subtree of root2: dfs(root1.left, root2.right)
    • Check if the right subtree of root1 mirrors the left subtree of root2: dfs(root1.right, root2.left)
    • Both conditions must be true for the trees to be symmetric, so we use and to combine them

Why This Works:

The algorithm effectively performs two synchronized DFS traversals:

  • One traversal goes left-then-right on the left subtree
  • The other goes right-then-left on the right subtree

At each step, we verify that the corresponding nodes have equal values. The recursive calls ensure we check all levels of the tree, from root to leaves.

Time Complexity: O(n) where n is the number of nodes, as we visit each node once Space Complexity: O(h) where h is the height of the tree, due to the recursive call 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 small example to illustrate how the solution works:

       1
      / \
     2   2
    / \   \
   3   4   3

We want to check if this tree is symmetric. Let's trace through the recursive calls:

Step 1: Start with isSymmetric(root) where root has value 1

  • Call dfs(root.left, root.right)dfs(node_2_left, node_2_right)

Step 2: Compare the two nodes with value 2

  • Both nodes exist and have value 2 ✓
  • Now check if their children are mirrors:
    • Call dfs(node_2_left.left, node_2_right.right)dfs(node_3, node_3)
    • Call dfs(node_2_left.right, node_2_right.left)dfs(node_4, None)

Step 3: First recursive call: dfs(node_3, node_3)

  • Both nodes exist and have value 3 ✓
  • Check their children:
    • Call dfs(None, None) for left-right pair → returns True
    • Call dfs(None, None) for right-left pair → returns True
  • Both are true, so this returns True

Step 4: Second recursive call: dfs(node_4, None)

  • One node exists (value 4) but the other is None
  • This is asymmetric, so returns False

Step 5: Back in Step 2

  • First recursive call returned True
  • Second recursive call returned False
  • Since True and False = False, the entire check returns False

The tree is not symmetric because node 4 on the left doesn't have a corresponding mirror node on the right.

For contrast, if the tree were:

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

The same process would find that:

  • Nodes with value 2 match ✓
  • Left child 3 matches right child 3 ✓
  • Right child 4 matches left child 4 ✓
  • All recursive calls return True, so the tree is symmetric

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
10class Solution:
11    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
12        """
13        Determines if a binary tree is symmetric around its center.
14        A tree is symmetric if its left and right subtrees are mirror images.
15      
16        Args:
17            root: The root node of the binary tree
18          
19        Returns:
20            True if the tree is symmetric, False otherwise
21        """
22      
23        def is_mirror(left_node: Optional[TreeNode], right_node: Optional[TreeNode]) -> bool:
24            """
25            Helper function to check if two subtrees are mirror images of each other.
26          
27            Args:
28                left_node: Root of the left subtree
29                right_node: Root of the right subtree
30              
31            Returns:
32                True if the subtrees are mirrors, False otherwise
33            """
34            # Both nodes are None - symmetric base case
35            if left_node is None and right_node is None:
36                return True
37          
38            # One node is None but not the other - not symmetric
39            if left_node is None or right_node is None:
40                return False
41          
42            # Values don't match - not symmetric
43            if left_node.val != right_node.val:
44                return False
45          
46            # Recursively check:
47            # - left child of left_node with right child of right_node
48            # - right child of left_node with left child of right_node
49            return (is_mirror(left_node.left, right_node.right) and 
50                    is_mirror(left_node.right, right_node.left))
51      
52        # An empty tree or single node is symmetric
53        if root is None:
54            return True
55          
56        # Check if left and right subtrees are mirrors of each other
57        return is_mirror(root.left, root.right)
58
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     * Determines if a binary tree is symmetric around its center.
19     * A tree is symmetric if its left subtree is a mirror reflection of its right subtree.
20     * 
21     * @param root The root node of the binary tree
22     * @return true if the tree is symmetric, false otherwise
23     */
24    public boolean isSymmetric(TreeNode root) {
25        // Check if left and right subtrees are mirrors of each other
26        return isMirror(root.left, root.right);
27    }
28
29    /**
30     * Helper method to check if two trees are mirror images of each other.
31     * Two trees are mirrors if:
32     * 1. Both are null (base case)
33     * 2. Both have same value and their children are mirrors in opposite positions
34     * 
35     * @param leftNode The root of the first tree
36     * @param rightNode The root of the second tree
37     * @return true if the two trees are mirrors, false otherwise
38     */
39    private boolean isMirror(TreeNode leftNode, TreeNode rightNode) {
40        // If both nodes are the same reference (including both null), they're symmetric
41        if (leftNode == rightNode) {
42            return true;
43        }
44      
45        // If only one is null or their values don't match, they're not symmetric
46        if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
47            return false;
48        }
49      
50        // Recursively check if:
51        // - left's left child mirrors right's right child
52        // - left's right child mirrors right's left child
53        return isMirror(leftNode.left, rightNode.right) && 
54               isMirror(leftNode.right, rightNode.left);
55    }
56}
57
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     * Determines if a binary tree is symmetric around its center
16     * @param root: Root node of the binary tree
17     * @return: true if the tree is symmetric, false otherwise
18     */
19    bool isSymmetric(TreeNode* root) {
20        // Handle edge case of null root
21        if (!root) {
22            return true;
23        }
24      
25        // Check if left and right subtrees are mirrors of each other
26        return isMirror(root->left, root->right);
27    }
28  
29private:
30    /**
31     * Helper function to check if two trees are mirrors of each other
32     * @param leftNode: Root of the left subtree
33     * @param rightNode: Root of the right subtree
34     * @return: true if the trees are mirrors, false otherwise
35     */
36    bool isMirror(TreeNode* leftNode, TreeNode* rightNode) {
37        // Both nodes are null - symmetric at this level
38        if (!leftNode && !rightNode) {
39            return true;
40        }
41      
42        // Only one node is null - not symmetric
43        if (!leftNode || !rightNode) {
44            return false;
45        }
46      
47        // Check if current nodes have same value
48        // Then recursively check:
49        // - left child of leftNode with right child of rightNode
50        // - right child of leftNode with left child of rightNode
51        return (leftNode->val == rightNode->val) &&
52               isMirror(leftNode->left, rightNode->right) &&
53               isMirror(leftNode->right, rightNode->left);
54    }
55};
56
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Checks if a binary tree is symmetric around its center
17 * @param root - The root node of the binary tree
18 * @returns true if the tree is symmetric, false otherwise
19 */
20function isSymmetric(root: TreeNode | null): boolean {
21    // Handle edge case where root is null
22    if (!root) {
23        return true;
24    }
25  
26    // Start the comparison from root's left and right subtrees
27    return isMirror(root.left, root.right);
28}
29
30/**
31 * Helper function to check if two subtrees are mirror images of each other
32 * @param leftNode - Node from the left subtree
33 * @param rightNode - Node from the right subtree
34 * @returns true if the subtrees are mirrors, false otherwise
35 */
36function isMirror(leftNode: TreeNode | null, rightNode: TreeNode | null): boolean {
37    // Both nodes are null - symmetric
38    if (leftNode === null && rightNode === null) {
39        return true;
40    }
41  
42    // Only one node is null - not symmetric
43    if (leftNode === null || rightNode === null) {
44        return false;
45    }
46  
47    // Check if current nodes have same value
48    if (leftNode.val !== rightNode.val) {
49        return false;
50    }
51  
52    // Recursively check:
53    // - left child of leftNode with right child of rightNode
54    // - right child of leftNode with left child of rightNode
55    return isMirror(leftNode.left, rightNode.right) && 
56           isMirror(leftNode.right, rightNode.left);
57}
58

Time and Space Complexity

Time Complexity: O(n), where n is the number of nodes in the binary tree.

The algorithm performs a depth-first search (DFS) traversal to check if the tree is symmetric. In the worst case, we need to visit every node in the tree exactly once. Each node is processed with O(1) operations (comparing values and making recursive calls). Therefore, the total time complexity is O(n).

Space Complexity: O(n) in the worst case, where n is the number of nodes in the binary tree.

The space complexity is determined by the recursive call stack. In the worst case, the tree could be completely unbalanced (e.g., a skewed tree forming a linked list), resulting in a recursion depth of O(n). In the best case of a perfectly balanced tree, the recursion depth would be O(log n), but we consider the worst-case scenario for complexity analysis. Therefore, the space complexity is O(n).

Common Pitfalls

1. Incorrect Mirror Comparison Direction

A frequent mistake is comparing the wrong pairs of nodes when checking for symmetry. Developers often incorrectly compare:

  • left_node.left with right_node.left (same side comparison)
  • left_node.right with right_node.right (same side comparison)

Incorrect Implementation:

# WRONG - This checks if subtrees are identical, not mirrors
return (is_mirror(left_node.left, right_node.left) and 
        is_mirror(left_node.right, right_node.right))

Correct Implementation:

# CORRECT - Cross comparison for mirror symmetry
return (is_mirror(left_node.left, right_node.right) and 
        is_mirror(left_node.right, right_node.left))

2. Forgetting to Handle the Root Node Properly

Some implementations mistakenly start the comparison from the root itself rather than its children:

Incorrect Implementation:

def isSymmetric(self, root: Optional[TreeNode]) -> bool:
    # WRONG - Comparing root with itself always returns True
    return is_mirror(root, root)

Correct Implementation:

def isSymmetric(self, root: Optional[TreeNode]) -> bool:
    if root is None:
        return True
    # CORRECT - Compare left and right subtrees
    return is_mirror(root.left, root.right)

3. Incomplete Base Case Handling

A critical error is not properly handling all combinations of None nodes:

Incorrect Implementation:

def is_mirror(left_node, right_node):
    # WRONG - Missing the case where only one node is None
    if left_node is None and right_node is None:
        return True
  
    # This will cause AttributeError if one node is None
    if left_node.val != right_node.val:
        return False

Correct Implementation:

def is_mirror(left_node, right_node):
    # Handle both None
    if left_node is None and right_node is None:
        return True
  
    # Handle exactly one None - must check before accessing .val
    if left_node is None or right_node is None:
        return False
  
    # Now safe to access .val
    if left_node.val != right_node.val:
        return False

4. Using OR Instead of AND in Recursive Calls

Some developers mistakenly use or when combining recursive results:

Incorrect Implementation:

# WRONG - This would return True if only one side is symmetric
return (is_mirror(left_node.left, right_node.right) or 
        is_mirror(left_node.right, right_node.left))

Correct Implementation:

# CORRECT - Both sides must be symmetric
return (is_mirror(left_node.left, right_node.right) and 
        is_mirror(left_node.right, right_node.left))

5. Not Considering Single Node Trees

While the provided solution handles this correctly, a common oversight is not recognizing that a tree with just a root node (no children) is symmetric:

# Tree with just root node
root = TreeNode(1)  
# Should return True, as a single node is symmetric

The solution correctly handles this by checking if both root.left and root.right are None, which returns True.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More