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:
- If both nodes are
None
(represented byroot1 == root2
when both areNone
), they are symmetric - If only one is
None
or their values differ, they are not symmetric - Otherwise, recursively check that:
- The left child of
root1
mirrors the right child ofroot2
- The right child of
root1
mirrors the left child ofroot2
- The left child of
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.
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:
- Have the same value
- 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:
-
Base Case - Both nodes are null:
- When
root1 == root2
(both areNone
), the trees are symmetric at this point, returnTrue
- This handles the leaf nodes and empty subtrees
- When
-
Asymmetric Cases:
- If only one of
root1
orroot2
isNone
, the structure is different, returnFalse
- If both nodes exist but
root1.val != root2.val
, the values don't match, returnFalse
- If only one of
-
Recursive Case:
- Check if the left subtree of
root1
mirrors the right subtree ofroot2
:dfs(root1.left, root2.right)
- Check if the right subtree of
root1
mirrors the left subtree ofroot2
:dfs(root1.right, root2.left)
- Both conditions must be true for the trees to be symmetric, so we use
and
to combine them
- Check if the left subtree of
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 EvaluatorExample 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)
- Call
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 → returnsTrue
- Call
dfs(None, None)
for right-left pair → returnsTrue
- Call
- 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 returnsFalse
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
withright_node.left
(same side comparison)left_node.right
withright_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.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
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
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!