Determine if a binary tree is vertically symmetric. That is, the left subtree of the root node is a mirror of the right subtree.
Output: True
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Fold the tree down its middle. It is symmetric if the left half lands exactly on the right half — like a butterfly’s wings or your reflection in a mirror. Note the flip: the left subtree’s left child must match the right subtree’s right child.
To check a binary tree's symmetry, we need to assess its left and right subtrees. The first thing to note is that the root node itself doesn’t affect the symmetry of the tree. Therefore, we don't need to consider the root node. We now have the task of comparing two subtrees to check if one vertically mirrors the other.
Consider the root node’s left and right subtrees in the following example:
The key observation here is that the right subtree is an inverted version of the left subtree.
We’ve learned from the problem Invert Binary Tree that an inversion is performed by swapping the left and right child of every node. This suggests the value of each node's left child in the left subtree should match the value of the right child of the corresponding node in the right subtree, and vice versa.
We can start by using DFS to traverse both subtrees. During this traversal, we compare the left and right children of each node in the left subtree with the right and left children of its corresponding node in the right subtree, respectively.
If the values of any two nodes being compared are not the same, the tree is not symmetric.
If, at any point, one of the child nodes being compared is null while the other isn’t, the tree is also not symmetric.
Initially, we see the values of the root nodes of the left and right subtrees are equal:
Since they’re equal, proceed by comparing their children through recursive DFS calls. Specifically, make two recursive DFS calls to compare the left child of one node with the right child of the other. This checks that node2’s children contain the same values as node1’s children, but inverted.
If either DFS call returns false, the subtrees are not symmetric. If both DFS calls return true, the subtrees are symmetric. This process is repeated for the entire tree.
from ds import TreeNode
def binary_tree_symmetry(root: TreeNode) -> bool:
if not root:
return True
return compare_trees(root.left, root.right)
def compare_trees(node1: TreeNode, node2: TreeNode) -> bool:
# Base case: if both nodes are null, they're symmetric.
if not node1 and not node2:
return True
# If one node is null and the other isn't, they aren't symmetric.
if not node1 or not node2:
return False
# If the values of the current nodes don't match, trees aren't symmetric.
if node1.val != node2.val:
return False
# Compare the 'node1's left subtree with 'node2's right subtree. If these
# aren't symmetric, the whole tree is not symmetric.
if not compare_trees(node1.left, node2.right):
return False
# Compare the 'node1's right subtree with 'node2's left subtree.
return compare_trees(node1.right, node2.left)bool binaryTreeSymmetry(TreeNode* root) {
if (root == nullptr) {
return true;
}
queue<pair<TreeNode*, TreeNode*>> q;
q.push({root->left, root->right});
while (!q.empty()) {
auto [a, b] = q.front();
q.pop();
if (a == nullptr && b == nullptr) {
continue;
}
if (a == nullptr || b == nullptr || a->val != b->val) {
return false;
}
q.push({a->left, b->right}); // cross pairs
q.push({a->right, b->left});
}
return true;
}boolean binaryTreeSymmetry(TreeNode root) {
if (root == null) return true;
Queue<TreeNode[]> q = new LinkedList<>();
q.add(new TreeNode[]{root.left, root.right});
while (!q.isEmpty()) {
TreeNode[] p = q.poll();
TreeNode a = p[0], b = p[1];
if (a == null && b == null) continue;
if (a == null || b == null || a.val != b.val) return false;
q.add(new TreeNode[]{a.left, b.right});
q.add(new TreeNode[]{a.right, b.left});
}
return true;
}Time complexity: The time complexity of binary_tree_symmetry is , where denotes the number of nodes in the tree. This is because we process each node recursively at most once.
Space complexity: The space complexity is due to the space taken up by the recursive call stack, which can grow as large as the height of the binary tree. The largest possible height of a binary tree is .
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
| compare pair (a, b) | vals | then check |
|---|---|---|
| (left 2, right 2) | 2 = 2 ✓ | (2.left, 2.right) & (2.right, 2.left) |
| (3, 3) | 3 = 3 ✓ | children all None |
| (4, 4) | 4 = 4 ✓ | children all None |
| all pairs matched | — | → true |
Every mirror pair has equal values and matching shape. symmetric = true ✓
| compare pair (a, b) | result |
|---|---|
| (left 2, right 2) | vals 2 = 2 ✓ → recurse cross-pairs |
| (2.left = None, 2.right = 3) | one is None, the other is not → false |
Both middle nodes are 2, so values match — but the left 2 has its child on the right, while a mirror needs it on the left. The cross-pair (None, 3) fails immediately. symmetric = false ✓
The nested mirror(a, b) reads like the definition. Tuples in the queue hold the pairs for the iterative version.
pair<TreeNode*, TreeNode*> holds each pair; structured bindings auto [a, b] unpack it. nullptr is the empty node.
A two-element TreeNode[] stands in for a pair (Java has no tuple). The three-way null/value check is identical to the others.
Tip: Cover null cases. Always check for null or empty inputs before using their attributes in a function. In this problem, the main binary_tree_symmetry function itself accesses the left and right attributes of the input node, necessitating an initial null check.