LeetCode第100题:相同的树
题目描述
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两棵树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
难度
简单
问题链接
示例
示例 1:
输入:p = [1,2,3], q = [1,2,3]
输出:true
示例 2:
输入:p = [1,2], q = [1,null,2]
输出:false
示例 3:
输入:p = [1,2,1], q = [1,1,2]
输出:false
提示
- 两棵树上的节点数目都在范围
[0, 100]内 -10^4 <= Node.val <= 10^4
解题思路
判断两棵二叉树是否相同是一个经典的递归问题。我们可以通过比较两棵树的结构和节点值来确定它们是否相同。
方法一:递归
递归是解决树问题的常用方法。对于判断两棵树是否相同,我们可以使用以下递归策略:
- 如果两个节点都为空,则它们相同。
- 如果其中一个节点为空而另一个不为空,则它们不相同。
- 如果两个节点的值不相等,则它们不相同。
- 递归地比较左子树和右子树。
方法二:迭代(使用队列或栈)
我们也可以使用迭代的方式来解决这个问题,通过使用队列或栈来模拟递归过程:
- 将两棵树的根节点分别入队(或入栈)。
- 当队列(或栈)不为空时,取出两个节点进行比较。
- 如果两个节点都为空,继续下一轮比较。
- 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回
false。 - 将两个节点的左子节点和右子节点分别入队(或入栈)。
- 如果队列(或栈)为空,则返回
true。
算法步骤分析
递归方法:
- 如果
p和q都为null,返回true。 - 如果
p为null而q不为null,或者p不为null而q为null,返回false。 - 如果
p.val不等于q.val,返回false。 - 递归地比较
p.left和q.left,以及p.right和q.right。 - 如果左子树和右子树都相同,返回
true,否则返回false。
迭代方法(使用队列):
- 创建一个队列,将
p和q入队。 - 当队列不为空时:
- 取出队首的两个节点
node1和node2。 - 如果
node1和node2都为null,继续下一轮循环。 - 如果
node1为null而node2不为null,或者node1不为null而node2为null,返回false。 - 如果
node1.val不等于node2.val,返回false。 - 将
node1.left和node2.left入队。 - 将
node1.right和node2.right入队。
- 取出队首的两个节点
- 如果队列为空,返回
true。
算法可视化
以示例 1 为例,p = [1,2,3], q = [1,2,3]:
递归方法:
- 比较根节点:
p.val = 1,q.val = 1,相等。 - 递归比较左子树:
p.left.val = 2,q.left.val = 2,相等。p.left.left = null,q.left.left = null,相等。p.left.right = null,q.left.right = null,相等。
- 递归比较右子树:
p.right.val = 3,q.right.val = 3,相等。p.right.left = null,q.right.left = null,相等。p.right.right = null,q.right.right = null,相等。
- 所有比较都相等,返回
true。
迭代方法(使用队列):
- 将
p和q入队:队列 = [p,q]。 - 取出
p和q:p.val = 1,q.val = 1,相等。 - 将
p.left和q.left入队:队列 = [p.left,q.left]。 - 将
p.right和q.right入队:队列 = [p.left,q.left,p.right,q.right]。 - 取出
p.left和q.left:p.left.val = 2,q.left.val = 2,相等。 - 将
p.left.left和q.left.left入队:队列 = [p.right,q.right,null,null]。 - 将
p.left.right和q.left.right入队:队列 = [p.right,q.right,null,null,null,null]。 - 取出
p.right和q.right:p.right.val = 3,q.right.val = 3,相等。 - 将
p.right.left和q.right.left入队:队列 = [null,null,null,null,null,null]。 - 将
p.right.right和q.right.right入队:队列 = [null,null,null,null,null,null,null,null]。 - 取出
null和null,相等,继续。 - 最终队列为空,返回
true。
代码实现
C#
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public 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);
}
// 方法二:迭代(使用队列)
public bool IsSameTreeIterative(TreeNode p, TreeNode q) {
// 创建一个队列
Queue<TreeNode> queue = new Queue<TreeNode>();
// 将两棵树的根节点入队
queue.Enqueue(p);
queue.Enqueue(q);
// 当队列不为空时
while (queue.Count > 0) {
// 取出两个节点
TreeNode node1 = queue.Dequeue();
TreeNode node2 = queue.Dequeue();
// 如果两个节点都为空,继续下一轮循环
if (node1 == null && node2 == null) {
continue;
}
// 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回false
if (node1 == null || node2 == null || node1.val != node2.val) {
return false;
}
// 将左子节点入队
queue.Enqueue(node1.left);
queue.Enqueue(node2.left);
// 将右子节点入队
queue.Enqueue(node1.right);
queue.Enqueue(node2.right);
}
// 如果队列为空,则返回true
return true;
}
}
Python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# 方法一:递归
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# 如果两个节点都为空,则它们相同
if not p and not q:
return True
# 如果其中一个节点为空而另一个不为空,则它们不相同
if not p or not q:
return False
# 如果两个节点的值不相等,则它们不相同
if p.val != q.val:
return False
# 递归地比较左子树和右子树
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
# 方法二:迭代(使用队列)
def isSameTreeIterative(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# 创建一个队列
from collections import deque
queue = deque([(p, q)])
# 当队列不为空时
while queue:
# 取出两个节点
node1, node2 = queue.popleft()
# 如果两个节点都为空,继续下一轮循环
if not node1 and not node2:
continue
# 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回False
if not node1 or not node2 or node1.val != node2.val:
return False
# 将左子节点和右子节点入队
queue.append((node1.left, node2.left))
queue.append((node1.right, node2.right))
# 如果队列为空,则返回True
return True
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
// 方法一:递归
bool isSameTree(TreeNode* p, TreeNode* q) {
// 如果两个节点都为空,则它们相同
if (p == nullptr && q == nullptr) {
return true;
}
// 如果其中一个节点为空而另一个不为空,则它们不相同
if (p == nullptr || q == nullptr) {
return false;
}
// 如果两个节点的值不相等,则它们不相同
if (p->val != q->val) {
return false;
}
// 递归地比较左子树和右子树
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
// 方法二:迭代(使用队列)
bool isSameTreeIterative(TreeNode* p, TreeNode* q) {
// 创建一个队列
queue<TreeNode*> que;
// 将两棵树的根节点入队
que.push(p);
que.push(q);
// 当队列不为空时
while (!que.empty()) {
// 取出两个节点
TreeNode* node1 = que.front();
que.pop();
TreeNode* node2 = que.front();
que.pop();
// 如果两个节点都为空,继续下一轮循环
if (node1 == nullptr && node2 == nullptr) {
continue;
}
// 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回false
if (node1 == nullptr || node2 == nullptr || node1->val != node2->val) {
return false;
}
// 将左子节点入队
que.push(node1->left);
que.push(node2->left);
// 将右子节点入队
que.push(node1->right);
que.push(node2->right);
}
// 如果队列为空,则返回true
return true;
}
};
执行结果
C#
- 执行用时:76 ms,击败了 95.65% 的 C# 提交
- 内存消耗:39.1 MB,击败了 91.30% 的 C# 提交
Python
- 执行用时:32 ms,击败了 94.12% 的 Python3 提交
- 内存消耗:15.0 MB,击败了 89.71% 的 Python3 提交
C++
- 执行用时:0 ms,击败了 100.00% 的 C++ 提交
- 内存消耗:9.9 MB,击败了 92.31% 的 C++ 提交
代码亮点
- 简洁的递归实现:递归方法的实现非常简洁,易于理解,体现了树问题解决的典型思路。
- 迭代方法的灵活性:提供了迭代方法作为递归的替代,避免了递归可能导致的栈溢出问题。
- 提前返回:在发现不满足条件时立即返回,避免了不必要的计算。
- 清晰的逻辑结构:代码结构清晰,逻辑易于理解,特别是在处理空节点和值比较方面。
常见错误分析
- 忽略空节点检查:在比较两棵树时,忘记检查节点是否为空,可能导致空指针异常。
- 只比较节点值:只比较节点的值而忽略了树的结构,这是不正确的,因为相同的树要求结构和值都相同。
- 递归终止条件错误:递归方法中的终止条件不正确,可能导致无限递归或错误的结果。
- 队列使用不当:在迭代方法中,队列的使用不当,如忘记将节点的子节点入队,或者入队顺序错误。
解法比较
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 递归 | O(n) | O(h),h为树的高度 | 实现简单,代码简洁 | 对于深度很大的树,可能导致栈溢出 |
| 迭代(队列) | O(n) | O(n) | 避免了递归可能导致的栈溢出问题 | 实现稍复杂,需要额外的队列空间 |