力扣解题-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⁴ <= Node.val <= 10⁴
Related Topics
树、深度优先搜索、广度优先搜索、二叉树
第一次解答
解题思路
核心方法:递归法(深度优先搜索 DFS),通过“自顶向下”的递归对比,逐节点校验两棵树的结构和值是否一致,基线条件覆盖“双空、一空一非空、值不同”三种不/同情况,逻辑简洁且时间/空间复杂度最优。
核心逻辑拆解
判断两棵树是否相同的核心是“逐节点校验+递归验证子树”,需同时满足结构相同和节点值相同:
- 基线条件1(双空节点):若
p == null && q == null,说明当前位置的节点都不存在,结构一致,返回true; - 基线条件2(结构不同):若
p == null || q == null(仅一个节点为空),说明结构不一致,返回false; - 基线条件3(值不同):若
p.val != q.val,说明节点值不一致,返回false; - 递归验证子树:
- 递归校验左子树:
isSameTree(p.left, q.left); - 递归校验右子树:
isSameTree(p.right, q.right);
- 递归校验左子树:
- 合并结果:只有左右子树都相同(
leftResult && rightResult),当前节点所在的子树才相同,返回最终布尔值。
具体步骤(以示例2 p=[1,2]、q=[1,null,2]为例)
| 递归层级 | 对比节点(p,q) | 校验结果 | 说明 |
|---|---|---|---|
| 1 | (1,1) | - | 值相同,递归校验子树 |
| 2 | (2,null) | false | 一空一非空,结构不同 |
| 2 | (null,2) | 未执行 | 左子树已返回false,短路 |
| 最终结果为false,与示例一致。 |
性能说明
- 时间复杂度:O(n)(n为两棵树中节点数的较小值,每个节点仅被对比一次);
- 空间复杂度:O(h)(h为两棵树中高度的较小值,递归调用栈的深度等于树的高度):
- 最好情况(平衡二叉树):h = log₂n,空间复杂度O(logn);
- 最坏情况(斜树):h = n,空间复杂度O(n);
- 优势:
- 代码极简,基线条件覆盖所有核心判断场景,逻辑无冗余;
- 短路特性:只要某一层级校验失败,后续递归直接终止,执行效率高;
- 天然处理空树、单节点树等边界场景。
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;
}
// 递归判断左右子树是否相同
boolean leftResult=isSameTree(p.left, q.left);
boolean rightResult=isSameTree(p.right, q.right);
return leftResult&&rightResult;
}
示例解答
解题思路
解法1:迭代法(广度优先搜索 BFS / 层序遍历)
核心方法:队列辅助层序对比,利用队列同时存储两棵树的对应节点,逐层逐节点校验结构和值是否一致,属于“自顶向下”的迭代实现,避免递归栈的调用,适合树高度较大的场景。
代码实现
import java.util.LinkedList;
import java.util.Queue;
public boolean isSameTree(TreeNode p, TreeNode q) {
// 队列存储两棵树的对应节点(成对存储)
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(p);
queue.offer(q);
while (!queue.isEmpty()) {
// 取出一对节点进行对比
TreeNode nodeP = queue.poll();
TreeNode nodeQ = queue.poll();
// 双空节点,跳过后续校验
if (nodeP == null && nodeQ == null) {
continue;
}
// 结构不同或值不同,直接返回false
if (nodeP == null || nodeQ == null || nodeP.val != nodeQ.val) {
return false;
}
// 成对加入左右子节点(保证顺序一致)
queue.offer(nodeP.left);
queue.offer(nodeQ.left);
queue.offer(nodeP.right);
queue.offer(nodeQ.right);
}
// 所有节点对比完成,均一致
return true;
}
核心逻辑说明
- 队列初始化:将两棵树的根节点成对入队,保证对比顺序一致;
- 层序遍历对比:
- 每次从队列中取出一对节点(
nodeP、nodeQ); - 双空节点:跳过(结构一致,无需处理);
- 结构/值不一致:直接返回
false; - 结构和值一致:将左右子节点成对入队(保证后续对比顺序);
- 每次从队列中取出一对节点(
- 返回结果:队列遍历完成后,说明所有节点都对比一致,返回
true。
性能说明
- 时间复杂度:O(n)(每个节点仅入队/出队一次);
- 空间复杂度:O(n)(最坏情况队列存储一层所有节点对,如完全二叉树的最后一层);
- 优势:非递归实现,避免递归栈溢出风险(如树高度极大时);
- 劣势:需要额外的队列空间,代码量略多于递归法。
解法2:迭代法(深度优先搜索 DFS / 栈模拟)
核心方法:栈模拟递归过程,用栈存储两棵树的对应节点,按“根→右→左”的顺序入栈(模拟递归的深度优先遍历),逐节点校验结构和值是否一致,逻辑与递归法等价但无递归栈开销。
代码实现
import java.util.Stack;
public boolean isSameTree(TreeNode p, TreeNode q) {
Stack<TreeNode> stack = new Stack<>();
// 根节点入栈
stack.push(p);
stack.push(q);
while (!stack.isEmpty()) {
// 取出一对节点(栈后进先出,先取q再取p)
TreeNode nodeQ = stack.pop();
TreeNode nodeP = stack.pop();
// 双空节点,跳过
if (nodeP == null && nodeQ == null) {
continue;
}
// 结构/值不一致
if (nodeP == null || nodeQ == null || nodeP.val != nodeQ.val) {
return false;
}
// 右子节点先入栈(保证左子节点先处理)
stack.push(nodeP.right);
stack.push(nodeQ.right);
// 左子节点后入栈
stack.push(nodeP.left);
stack.push(nodeQ.left);
}
return true;
}
核心逻辑说明
- 栈初始化:将两棵树的根节点成对入栈;
- 栈遍历对比:
- 弹出一对节点(注意栈后进先出,先弹q再弹p);
- 校验逻辑与递归法一致;
- 按“右→左”顺序入栈子节点(保证左子节点先被处理,与递归DFS顺序一致);
- 返回结果:栈遍历完成后返回
true。
性能说明
- 时间复杂度:O(n)(每个节点仅入栈/出栈一次);
- 空间复杂度:O(h)(h为树的高度,栈的深度等于树的高度);
- 优势:非递归实现,可控性更高,避免递归栈溢出;
- 劣势:代码量多于递归法,需要手动管理栈的入栈/出栈顺序。
总结
- 递归DFS法(第一次解答):O(n)时间+O(h)空间,代码极简、逻辑直观,是判断相同树的最优解法,工程中优先使用;
- 迭代BFS法(层序遍历):O(n)时间+O(n)空间,非递归实现,适合需要避免递归栈的场景;
- 迭代DFS法(栈模拟):O(n)时间+O(h)空间,与递归法空间复杂度一致,代码稍复杂;
- 关键技巧:
- 核心思想:判断相同树需同时满足“结构相同+节点值相同”,逐节点校验是核心;
- 基线条件:优先判断“双空→一空一非空→值不同”,覆盖所有核心不一致场景;
- 方法选择:优先选递归DFS法(代码简洁),树高度极大时选迭代法(避免栈溢出)。