Problem overview
The method IsSameTree receives the roots of two binary trees, p and q, and returns true when they are the same tree: exactly the same shape, and the same value in every position. Two empty trees are the same. The lesson provides the TreeNode class with the fields val, left and right.
[1, 2, 3]and[1, 2, 3]are the same.[1, 2]and[1, null, 2]hold the same values but are different: the 2 is the left child in one tree and the right child in the other.[1, 2, 1]and[1, 1, 2]have the same shape but different values: the left children are 2 and 1.
Approach: compare the roots, then the subtrees
Two trees are the same when their roots hold the same value, their left subtrees are the same, and their right subtrees are the same. The two subtree questions are the same problem on smaller trees, so recursion fits. The recursion needs three situations to be settled before it goes deeper:
- Both nodes are missing. Two empty trees are the same, so the answer is
true. This is also where the recursion stops, when both sides step past a leaf together. - Exactly one node is missing. The shapes differ, so the answer is
false. - Both nodes exist but hold different values. The answer is
false.
If none of those applies, the two roots match, and the trees are the same exactly when both pairs of subtrees are. Each pair of nodes is compared at most once, so the time is O(n).
Step by step
- Both null: if
p == null && q == null, returntrue. - One null: if
p == null || q == null, returnfalse. The case of both being null was handled above, so here exactly one tree is empty and the other isn't. - Different values: if
p.val != q.val, returnfalse. Both nodes exist at this point, so reading their values is safe. - Recurse: return
IsSameTree(p.left, q.left) && IsSameTree(p.right, q.right). When the left pair already differs,&&skips the right pair.
The solution in C#
class Solution {
public bool 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);
}
}
The C# details worth knowing:
- The order of the checks matters. The value test
p.val != q.valcomes after both null tests. If either node were null, readingvalwould throw aNullReferenceException. - Compare values, not references.
TreeNodeis a class, sop == qwould ask whether both variables point to the same object, and two separately built trees never do. The solution compares thevalfields and walks the structure. nulltests with==. For a class without its own==operator,p == nullsimply checks that the reference is empty.- Short-circuit
&&. The right-hand call runs only if the left-hand call returnedtrue, which saves work on trees that differ early.
Complexity
Time: O(n), where n is the number of nodes, because each pair of nodes is compared at most once, with constant work per pair.
Space: O(h), where h is the height of the tree, for the recursion. At most 400 levels deep here, so the default stack is enough. A degenerate chain of nodes would use O(n) stack.
Edge cases and common mistakes
- Both trees empty. The result is
true, since both nodes are null at once. - One tree empty. With
[1]and an empty tree, the result isfalse. - Same values, different shape.
[1, 2]against[1, null, 2]returnsfalse, because the left children are a node andnull. Comparing the values in level order and ignoring the shape would miss this. - Same shape, different values.
[1, 2, 1]against[1, 1, 2]returnsfalseat the left children. - Checking only one null. A test such as
p == nullalone, followed byq.val, crashes whenqis the null one. - Comparing the nodes with
==. This tests identity, not equality of content.
Testing
p = [1, 2, 3],q = [1, 2, 3]givestrue.p = [1, 2],q = [1, null, 2]givesfalse.p = [1, 2, 1],q = [1, 1, 2]givesfalse.p = [],q = []givestrue.p = [1],q = []givesfalse.
