1. 问题背景与核心概念
今天我们来聊聊LeetCode上两道经典的二叉树题目——第100题"相同的树"和第101题"对称二叉树"。这两道题看似简单,却涵盖了二叉树遍历和递归思想的精髓,是理解树结构操作的绝佳入门。
二叉树是一种每个节点最多有两个子节点的树结构,在计算机科学中应用广泛。理解二叉树的操作对于解决更复杂的树形结构问题至关重要。这两道题目虽然题目编号相邻,但考察的侧重点有所不同:
- "相同的树"(LeetCode 100)要求判断两棵二叉树是否完全相同
- "对称二叉树"(LeetCode 101)则要求判断一棵二叉树是否是自身的镜像
注意:虽然标题提到的是LeetCode 116,但根据内容判断应为100和101题,116题实际是"填充每个节点的下一个右侧节点指针"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 相同的树(LeetCode 100)解法详解
2.1 问题描述
给定两棵二叉树的根节点p和q,编写一个函数来检验它们是否相同。如果两棵树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例:
code复制输入: 1 1
/ \ / \
2 3 2 3
[1,2,3], [1,2,3]
输出: true
2.2 递归解法
递归是解决树问题的天然工具,因为树本身就是递归定义的数据结构。对于"相同的树"问题,我们可以这样思考:
- 如果两棵树都为空,它们当然相同
- 如果其中一棵为空而另一棵不为空,它们不相同
- 如果两棵树的根节点值不同,它们不相同
- 如果以上条件都满足,则需要递归检查左右子树是否相同
Python实现:
python复制def isSameTree(p, q):
if not p and not q:
return True
if not p or not q:
return False
if p.val != q.val:
return False
return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)
时间复杂度:O(n),需要遍历所有节点
空间复杂度:O(h),h为树的高度,递归调用栈的深度
2.3 迭代解法
虽然递归解法简洁,但了解迭代解法也很重要,特别是对于可能出现的栈溢出情况。我们可以使用队列来进行层序遍历:
python复制from collections import deque
def isSameTree(p, q):
queue = deque([(p, q)])
while queue:
node1, node2 = queue.popleft()
if not node1 and not node2:
continue
if not node1 or not node2:
return False
if node1.val != node2.val:
return False
queue.append((node1.left, node2.left))
queue.append((node1.right, node2.right))
return True
3. 对称二叉树(LeetCode 101)解法详解
3.1 问题描述
给定一个二叉树的根节点root,检查它是否是自身的镜像(即对称的)。
示例:
code复制输入: 1
/ \
2 2
/ \ / \
3 4 4 3
输出: true
3.2 递归解法
对称二叉树的判断可以转化为:判断一棵树的左子树和右子树是否是镜像关系。我们可以定义一个辅助函数来比较两棵树是否是镜像:
- 两棵树都为空时是镜像
- 一棵为空另一棵不为空时不是镜像
- 根节点值不同时不是镜像
- 递归检查第一棵树的左子树与第二棵树的右子树,以及第一棵树的右子树与第二棵树的左子树
Python实现:
python复制def isSymmetric(root):
def isMirror(t1, t2):
if not t1 and not t2:
return True
if not t1 or not t2:
return False
return (t1.val == t2.val) and isMirror(t1.left, t2.right) and isMirror(t1.right, t2.left)
return isMirror(root, root)
3.3 迭代解法
同样,我们可以使用队列来实现迭代解法:
python复制from collections import deque
def isSymmetric(root):
queue = deque([root, root])
while queue:
t1 = queue.popleft()
t2 = queue.popleft()
if not t1 and not t2:
continue
if not t1 or not t2:
return False
if t1.val != t2.val:
return False
queue.append(t1.left)
queue.append(t2.right)
queue.append(t1.right)
queue.append(t2.left)
return True
4. 关键区别与常见误区
虽然这两道题都涉及二叉树的比较,但它们有以下关键区别:
-
比较对象不同:
- 相同的树:比较两棵不同的树
- 对称二叉树:比较一棵树的左右子树
-
递归调用的方式不同:
- 相同的树:左比左,右比右
- 对称二叉树:左比右,右比左
常见误区包括:
- 混淆两题的递归调用方式
- 忽略空节点的处理
- 在迭代解法中忘记处理所有可能的空节点情况
5. 复杂度分析与优化思路
5.1 时间复杂度
两种解法的时间复杂度都是O(n),因为我们需要访问树中的每个节点一次。
5.2 空间复杂度
- 递归解法:O(h),h为树的高度,取决于递归调用栈的深度
- 迭代解法:O(n),在最坏情况下需要存储所有节点
优化思路:
- 对于非常深的树,迭代解法可能更安全,避免栈溢出
- 可以尝试提前终止,发现不匹配立即返回,减少不必要的比较
- 对于特定形状的树(如完全二叉树),可以优化空间使用
6. 实际应用与扩展思考
这两道题目虽然简单,但体现了树结构处理的基本模式,在实际开发中有广泛应用:
- 文件系统比较:判断两个目录结构是否相同
- 配置验证:检查配置树是否符合特定对称要求
- 数据一致性检查:比较两个数据结构的差异
扩展思考:
- 如何找出两棵树的不同之处而不仅仅是判断是否相同?
- 如何判断一棵树是否是另一棵树的子树?
- 如何处理带环的图结构(这时简单的递归会导致无限循环)?
7. 个人实战经验分享
在实际刷题和面试中,我有以下几点经验分享:
- 递归解法虽然简洁,但一定要先考虑基线条件(base case),这是最容易出错的地方
- 对于树问题,先考虑空节点的情况总是没错的
- 迭代解法虽然代码稍长,但在面试中展示多种解法能体现全面的思维能力
- 可以先用小例子手动模拟递归过程,确保理解正确
- 对称二叉树的题目常常会变形为"判断两棵树是否互为镜像",原理类似但要注意比较方向
一个常见的陷阱是忘记处理节点值相同但结构不同的情况,例如:
code复制 1 1
/ \
2 2
这两棵树节点值相同但结构不同,不是相同的树,也不是对称的。
在解决这类问题时,我建议按照以下步骤思考:
- 明确问题要求(是比相同还是比对称)
- 考虑空节点情况
- 设计递归或迭代的遍历顺序
- 处理节点值的比较
- 组合子问题的结果
