LeetCode简单难度的经典二叉树题目------100. 相同的树,这道题虽然难度不高,但非常适合入门二叉树的遍历思想,尤其是递归和迭代两种核心思路的对比练习,新手朋友可以重点看看,老手也可以快速回顾巩固一下。
先简单梳理一下题目要求,避免踩坑:给两棵二叉树的根节点p和q,判断这两棵树是否"相同"。这里的相同有两个核心条件,缺一不可:结构上完全一致 ,并且对应位置的节点值完全相等。
举个直观的例子:如果p是一棵只有根节点(值为1)的树,q也是只有根节点(值为1),那它们是相同的;但如果p的根节点是1、左孩子是2,q的根节点是1、右孩子是2,哪怕节点值都一样,结构不同,也不算相同。
一、题目前置准备
题目已经给出了二叉树节点的定义,用TypeScript实现的,这里再贴一遍,方便大家对照代码理解(注释已补充,新手可重点看构造函数的逻辑):
typescript
class TreeNode {
val: number; // 节点值
left: TreeNode | null; // 左孩子,可能为null(没有左孩子)
right: TreeNode | null; // 右孩子,可能为null(没有右孩子)
// 构造函数:初始化节点,val默认0,左右孩子默认null
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = (val === undefined ? 0 : val); // 节点值为空时,默认设为0
this.left = (left === undefined ? null : left); // 左孩子为空时,默认设为null
this.right = (right === undefined ? null : right); // 右孩子为空时,默认设为null
}
}
二、解法一:递归解法
1. 递归思路分析
递归的核心思想是"分而治之",把判断两棵大树是否相同,拆解成判断无数个"小问题"------判断当前两个节点是否相同,以及它们的左孩子、右孩子是否分别相同。
递归的终止条件(也是边界情况)很关键,分三步判断,逻辑层层递进:
-
如果p和q都为null(两个节点都不存在):说明这两个位置的节点是相同的,返回true;
-
如果p和q中一个为null、另一个不为null(一个节点存在,一个不存在):说明结构不同,返回false;
-
如果p和q都不为null,但它们的val不相等(节点值不同):说明节点不相同,返回false;
如果以上三种情况都不满足,说明当前两个节点是相同的,接下来就递归判断它们的左孩子(p.left和q.left)和右孩子(p.right和q.right),只有左右孩子都相同,整棵树才相同(用&&连接两个递归结果)。
2. 递归代码实现
typescript
// 递归解法:isSameTree_1
function isSameTree_1(p: TreeNode | null, q: TreeNode | null): boolean {
// 边界情况1:两个节点都为空,相同
if (p === null && q === null) {
return true;
}
// 边界情况2:一个为空,一个不为空,结构不同,不相同
if (p === null || q === null) {
return false;
}
// 边界情况3:两个节点都不为空,但值不同,不相同
if (p.val !== q.val) {
return false;
}
// 递归:当前节点相同,判断左孩子和右孩子是否都相同
return isSameTree_1(p.left, q.left) && isSameTree_1(p.right, q.right);
};
3. 递归解法总结
优点:代码极度简洁,逻辑清晰,完全贴合二叉树的递归特性,容易理解和编写,新手友好;
缺点:递归依赖调用栈,如果二叉树深度极深(比如链式二叉树),可能会出现栈溢出的情况(但LeetCode的测试用例一般不会卡这种极端情况,日常刷题完全够用);
时间复杂度:O(n),n是两棵树中节点数较少的那一个,每个节点只会被访问一次;
空间复杂度:O(h),h是树的高度,最坏情况下(链式树)h=n,最好情况下(平衡树)h=logn。
三、解法二:迭代解法(用栈模拟递归,避免栈溢出)
1. 迭代思路分析
迭代解法的核心是"用栈模拟递归的调用过程",通过手动维护一个栈,把需要判断的节点对(p的节点和q的对应节点)压入栈中,然后循环弹出节点对进行判断,本质上和递归的逻辑是一致的,只是实现方式不同。
具体步骤:
-
先判断两棵树的根节点是否都为空(和递归边界1一致),如果是,直接返回true;
-
如果根节点一个为空、一个不为空(和递归边界2一致),直接返回false;
-
初始化一个栈,把根节点对(p和q)压入栈中(注意压入顺序,后续弹出时要对应);
-
循环:只要栈不为空,就弹出两个节点(pNode和qNode),进行判断;
-
判断弹出的两个节点:如果都为空,跳过(继续判断下一组节点);如果一个为空一个不为空,返回false;如果值不相等,返回false;
-
如果当前节点对相同,就把它们的左孩子对、右孩子对依次压入栈中(注意压入顺序,先压右孩子,再压左孩子,因为栈是"后进先出",和递归的顺序保持一致);
-
循环结束后,说明所有节点对都判断完毕,没有发现不相同的情况,返回true。
这里有个小细节:压入栈的顺序是"p.left、q.left、p.right、q.right",弹出的时候会先弹出p.right和q.right,再弹出p.left和q.left,和递归时"先判断左孩子,再判断右孩子"的顺序是一致的,不影响结果,但要注意保持对应关系,不能压混。
2. 迭代代码实现
typescript
// 迭代解法:isSameTree_2(栈模拟递归)
function isSameTree_2(p: TreeNode | null, q: TreeNode | null): boolean {
// 先处理根节点的边界情况(和递归一致)
if (p === null && q === null) {
return true;
}
if (p === null || q === null) {
return false;
}
// 初始化栈,压入根节点对(p和q)
let stack: (TreeNode | null)[] = [];
stack.push(p);
stack.push(q);
// 循环:栈不为空时,持续判断节点对
while (stack.length > 0) {
// 弹出两个节点,注意栈是后进先出,所以先弹出q,再弹出p(对应压入顺序)
let qNode: TreeNode | null = stack.pop() ?? null;
let pNode: TreeNode | null = stack.pop() ?? null;
// 两个节点都为空,跳过(继续判断下一组)
if (pNode === null && qNode === null) {
continue;
}
// 一个为空一个不为空,结构不同,返回false
if (pNode === null || qNode === null) {
return false;
}
// 节点值不同,返回false
if (pNode.val !== qNode.val) {
return false;
}
// 当前节点对相同,压入它们的左孩子对和右孩子对(保持对应关系)
stack.push(pNode.left);
stack.push(qNode.left);
stack.push(pNode.right);
stack.push(qNode.right);
}
// 所有节点对都判断完毕,没有不相同的情况,返回true
return true;
}
3. 迭代解法总结
优点:不依赖递归调用栈,避免了极端情况下的栈溢出问题,稳定性更好;
缺点:代码比递归稍长,需要手动维护栈和循环逻辑,对新手来说稍微复杂一点;
时间复杂度:O(n),和递归一致,每个节点只会被压入栈、弹出栈各一次,访问一次;
空间复杂度:O(n),最坏情况下(平衡树),栈中会存储n/2个节点对,空间复杂度为O(n);最好情况下(链式树),栈中最多存储2个节点对,空间复杂度为O(1)。
四、两种解法对比 & 刷题建议
| 解法类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 递归 | 代码简洁、逻辑清晰、易编写 | 极端情况下可能栈溢出 | 日常刷题、二叉树深度不深的场景 |
| 迭代(栈) | 无栈溢出问题、稳定性好 | 代码稍长、需维护栈逻辑 | 二叉树深度极深、生产环境场景 |
刷题建议:新手先掌握递归解法,因为它最贴合二叉树的特性,后续做二叉树的遍历、对称树、翻转树等题目时,思路可以无缝迁移;掌握递归后,再理解迭代解法,重点体会"栈模拟递归"的思想,这是二叉树迭代题目的核心套路。
五、常见踩坑点提醒
-
踩坑点1:忽略"结构不同"的情况,只判断节点值。比如p有左孩子、q没有左孩子,但其他节点值都相同,此时会误判为相同;
-
踩坑点2:递归时忘记写终止条件,导致无限递归,栈溢出;
-
踩坑点3:迭代时压入栈的节点对顺序混乱,导致弹出时判断的不是"对应节点"(比如把p.left和q.right压在一起);
-
踩坑点4:处理节点为null时不严谨,比如用p.val === q.val时,没有先判断p和q是否为null,导致空指针错误(代码中已规避此问题)。
六、总结
LeetCode 100. 相同的树,本质上是二叉树"同步遍历"的入门练习,核心是判断"结构+节点值"是否双匹配。递归解法胜在简洁,迭代解法胜在稳定,两种解法的逻辑完全一致,只是实现方式不同。
刷这道题的重点不在于"写出代码",而在于理解"如何同步判断两棵树的对应节点",这个思路后续会用到很多类似题目中(比如101. 对称二叉树,只是判断的是"自身的左孩子和右孩子",逻辑高度相似)。