Problem overview
The method isSameTree receives the roots of two binary trees, p and q, and returns true if 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 place in the other tree. Two empty trees are the same. The lesson provides TreeNode, with an int val and two TreeNode * fields, left and right.
If both trees have root 1 with left child 2 and right child 3, the answer is true. If p is root 1 with a left child 2, and q is root 1 with a right child 2, the answer is false: the values match, but the 2 is in a different place. If p has root 1 with children 2 and 1, and q has root 1 with children 1 and 2, the shapes match and the left children hold different values, so the answer is false.
Approach: compare the roots, then both pairs of subtrees
A tree is its root together with its left and right subtrees, and those are trees too. So two trees are the same exactly when:
- their roots are both missing, or both present and holding the same value, and
- their left subtrees are the same, and
- their right subtrees are the same.
That is a recursive definition, and it turns directly into a recursive function. Each call compares one node of p with the node in the same position of q, then hands the two pairs of children to itself. Each pair of nodes is compared at most once, so the time is O(n), where n is the number of nodes.
Step by step
- Both empty: if
!p && !q, returntrue. Two empty trees are the same, and this test also ends the recursion when both sides step past a leaf at once. - Exactly one empty: if
!p || !q, returnfalse. The case of both being empty already returned, so at this point one tree is empty and the other is not, and the shapes differ. - Different values: if
p->val != q->val, returnfalse. Both nodes exist here, so reading their values is safe. - Compare the children: return
isSameTree(p->left, q->left) && isSameTree(p->right, q->right). The roots match, so the trees are the same exactly when both pairs of subtrees are.
The order of the first two tests matters. The test for "exactly one empty" is only correct because the "both empty" case was handled before it.
The solution in C++
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (!p && !q) {
return true;
}
if (!p || !q) {
return false;
}
if (p->val != q->val) {
return false;
}
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
C++ details worth a closer look:
- Null pointers in conditions. A null pointer is false and any other pointer is true, so
!pis true exactly whenpisnullptr. The tree's missing children are stored as null pointers (the lesson'sTreeNodesets them toNULL). - Comparing pointers would be wrong.
p == qasks whether the two variables point to the same node in memory. Two separate trees with equal contents have different addresses, so the function compares values withp->val != q->valinstead. - Short-circuit
&&. If the left subtrees differ, the call on the right subtrees never runs. The answer is alreadyfalse. - Arrow versus dot.
pandqare pointers to nodes, so fields are read with->.
Complexity
Time: O(n), where n is the number of nodes in the smaller tree, at worst the number of nodes in either. Every call compares one pair of positions and makes at most two further calls, and a position is never compared twice. When the trees differ, the function can stop earlier.
Space: O(h), where h is the height, for the recursion stack. A balanced tree has h around log n, and a chain has h equal to n. The constraints keep the depth at 400 levels or less.
Edge cases and common mistakes
- Both trees empty.
isSameTree(nullptr, nullptr)returnstrue, from the first test. - One empty tree. An empty tree and a single node
0are different, in either order. The second test returnsfalsebefore any value is read. - Same values, different shape. Root 1 with left child 2 against root 1 with right child 2 gives
false, because the left children are a node and an empty tree. - Same shape, different values. Two single nodes
7and8givefalse. - Reading a value too early. Writing
p->val != q->valbefore the null tests dereferences a null pointer when one tree is shorter, which is undefined behaviour. - Comparing only values. Visiting both trees and comparing the lists of values would miss differences in shape.
Testing
p = [1, 2, 3]andq = [1, 2, 3]givestrue.p = [1, 2]andq = [1, NULL, 2]givesfalse.p = [1, 2, 1]andq = [1, 1, 2]givesfalse.- Two empty trees give
true. - An empty tree and the single node
0givefalse, in either order. - The single nodes
7and7givetrue, while7and8givefalse.
