Problem overview
The method isSameTree receives the roots of two binary trees, p and q, and returns true when they are the same tree. Two trees are the same when they have exactly the same shape and every node holds the same value as the node in the same position in the other tree. Two empty trees are the same. The lesson provides the TreeNode class, with an int val, a TreeNode left, a TreeNode right and a constructor TreeNode(int x).
The trees [1, 2, 3] and [1, 2, 3] are the same. The trees [1, 2] and [1, null, 2] hold the same values, but the 2 is a left child in the first and a right child in the second, so they differ. The trees [1, 2, 1] and [1, 1, 2] have the same shape, but their left children hold 2 and 1, so they differ too.
Approach: compare the roots, then the subtrees
Two trees are the same exactly when their roots hold the same value, their left subtrees are the same, and their right subtrees are the same. Each subtree is itself a tree, so the question repeats on smaller trees: recursion fits.
The recursion needs base cases for the places where a tree runs out. Looking at a pair of nodes, one of three things holds:
- Both are
null. Two empty trees are the same, so the answer istrue. This is also where the recursion ends when both sides step past a leaf together. - Exactly one is
null. One tree has a node where the other has nothing, so the shapes differ and the answer isfalse. - Both exist. Compare their values. If they differ, the answer is
false. Otherwise the trees are the same exactly when both pairs of subtrees are.
Each pair of nodes is compared at most once, so the algorithm runs in O(n) time, where n is the number of nodes.
Step by step
- If both
pandqarenull, returntrue. This handles two empty trees and the end of a branch that finishes on both sides at once. - If one of them is
null, returnfalse. The first check already took the case of both being empty, so here exactly one is empty and the shapes differ. - If
p.val != q.val, returnfalse. Both nodes exist at this point, so reading their values is safe. - Return the two subtree comparisons joined with
&&:isSameTree(p.left, q.left)andisSameTree(p.right, q.right). The roots already match, so the trees are the same exactly when both pairs of subtrees are.
The solution in Java
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null) {
return false;
}
if (p.val != q.val) {
return false;
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
Java details worth noticing:
- The order of the checks matters. Reading
p.valon anullreference throwsNullPointerException. A version that compared the values first, called with an empty tree and the tree[1], ended with that exception. The twonullchecks must come before the value check. ==on references.p == nullis the right test for "no node". Comparingp == qwould ask whether they are the very same object, which is a different question: two separate trees with equal contents are the same tree for this problem, but not the same object.intcompares with!=.valis a primitiveint, sop.val != q.valcompares values directly, with noequalsneeded.- Short-circuit
&&. When the left pair already differs, the right pair is not examined. - Recursion depth. Each tree is at most 400 levels deep, which the default Java stack handles easily. Two identical chains of 400 nodes compare as the same, and the same chains with one different node at the bottom compare as different.
Complexity
Time: O(n). Each call looks at one pair of nodes and does constant work, and no pair is visited twice. The recursion stops at the first mismatch, so unequal trees often finish sooner.
Space: O(h), where h is the height of the trees, for the call stack. It is at most 400 here.
Edge cases and common mistakes
- Both trees empty. The result is
true, from the first check. - One tree empty. An empty tree against
[1], in either order, givesfalse. - Same values, different shape.
[1, 2]against[1, null, 2]givesfalse. Likewise[1, 2, null, 3]against[1, 2, null, null, 3]givesfalse, because the 3 is a left child in one tree and a right child in the other. Comparing only the sequence of values, without the empty positions, would miss this. - Same shape, different values.
[1, 2, 1]against[1, 1, 2]givesfalse. Checking only the shape is not enough. - Different sizes.
[1, 2, 3]against[1, 2]givesfalse: the right child of the first root has no counterpart. - A single node.
[1]against[1]givestrue, and[1]against[2]givesfalse. - Checking only one null. Testing just
p == nullleavesq.valto fail whenqis the empty one.
Testing
[1, 2, 3]and[1, 2, 3]givetrue.[1, 2]and[1, null, 2]givefalse.[1, 2, 1]and[1, 1, 2]givefalse.[]and[]givetrue.[]and[1]givefalse.[1]and[2]givefalse.[1, 2, 3]and[1, 2]givefalse.[-10000]and[-10000]givetrue.
