非力扣hot100-二叉树专题-刷题笔记(一)

一、二叉树的中序遍历(递归 + 迭代双解法)

1. 题目核心定义

  1. 遍历定义 :中序遍历的顺序是 左子树 → 根节点 → 右子树,即先访问左子树的所有节点,再访问根节点,最后访问右子树的所有节点。
  2. 输入 :一棵二叉树的根节点 root
  3. 输出:按中序遍历顺序排列的节点值列表。
  4. 边界情况 :输入为空树(root == null)时,返回空列表。

2. 核心解法逻辑

方法一:递归解法(最直观)

  • 核心思想:利用递归的栈调用天然模拟遍历顺序,先递归处理左子树,再记录当前节点值,最后递归处理右子树。
  • 执行流程
    1. 若当前节点为空,直接返回;
    2. 递归遍历当前节点的左子树
    3. 将当前节点的值加入结果列表;
    4. 递归遍历当前节点的右子树
  • 关键规则:递归终止条件必须判断节点是否为空,否则会出现空指针异常。

方法二:迭代解法(面试高频)

  • 核心思想:用栈手动模拟递归调用栈,通过指针追踪当前节点,优先深入左子树,再回溯处理根节点,最后转向右子树。
  • 执行流程
    1. 初始化栈和结果列表,指针 cur 指向根节点;
    2. cur 不为空或栈不为空时,循环执行:
      • cur 不为空,将 cur 入栈,cur 移动到其左子节点(持续深入左子树);
      • cur 为空,弹出栈顶节点,将节点值加入结果列表,cur 移动到该节点的右子节点;
    3. 循环结束,返回结果列表。
  • 关键规则:必须先处理完所有左子节点,再处理根节点,最后处理右子节点,严格遵循中序顺序。

3. 标准模板代码(Java 版)

递归实现

复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    private void inorder(TreeNode node, List<Integer> res) {
        if (node == null) {
            return;
        }
        inorder(node.left, res);  // 遍历左子树
        res.add(node.val);        // 访问根节点
        inorder(node.right, res); // 遍历右子树
    }
}

迭代实现

复制代码
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
            // 持续深入左子树
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            // 左子树处理完毕,弹出根节点
            cur = stack.pop();
            res.add(cur.val);
            // 转向右子树
            cur = cur.right;
        }
        return res;
    }
}

4. 代码关键细节

  1. 递归解法
    • 必须传递结果列表作为参数,避免频繁创建新集合导致性能损耗;
    • 终止条件 node == null 是递归退出的核心,不可省略。
  2. 迭代解法
    • 使用 Deque 作为栈(Java 中推荐用 LinkedList 实现),避免使用 Stack 类;
    • 内层循环负责将所有左子节点入栈,确保左子树优先处理;
    • 弹出节点后必须将 cur 指向其右子节点,以继续处理右子树。
  3. 空树处理:两种解法都天然支持空树输入,直接返回空列表,无需额外判断。

5. 复杂度分析(面试必说)

  • 时间复杂度O(n),其中 n 是二叉树的节点数。每个节点都被访问且仅访问一次。
  • 空间复杂度
    • 递归解法:O(h)h 是二叉树的高度,递归调用栈的深度取决于树的高度,最坏情况(链状树)为 O(n)
    • 迭代解法:O(h),栈的最大存储深度等于树的高度,最坏情况为 O(n)

6. 典型场景验证

场景 1:普通二叉树

复制代码
    1
     \
      2
     /
    3
  • 中序遍历结果:[1, 3, 2]
  • 递归执行路径:inorder(1) → inorder(null) → 加入1 → inorder(2) → inorder(3) → inorder(null) → 加入3 → inorder(null) → 加入2
  • 迭代执行路径:1入栈 → cur=1.right=2 → 2入栈 → cur=2.left=3 → 3入栈 → cur=3.left=null → 弹出3(加入3)→ cur=3.right=null → 弹出2(加入2)→ cur=2.right=null → 弹出1(加入1)→ cur=1.right=2(已处理)

场景 2:空树

  • 输入:root = null
  • 输出:[]

场景 3:单节点树

  • 输入:root = new TreeNode(5)
  • 输出:[5]

7. 面试答题话术

我会用递归 + 迭代两种方法解决二叉树中序遍历问题:

  1. 递归解法 最直观,核心是遵循「左→根→右」的顺序,先递归左子树,再记录根节点值,最后递归右子树,时间复杂度 O(n),空间复杂度由递归栈深度决定。
  2. 迭代解法 是面试重点,用栈模拟递归过程,先把所有左子节点入栈,再弹出节点记录值,最后转向右子树,同样保证时间复杂度 O(n),空间复杂度 O(h)

两种解法都能覆盖空树、单节点、普通树等所有场景,迭代解法更能体现对栈和遍历过程的理解。

二、二叉树相同性判断


1、题目核心定义

  1. 问题描述 :给定两棵二叉树的根节点 pq,判断这两棵树是否完全相同(结构相同 + 对应节点值相同)。
  2. 核心要求
    • 结构相同:每个位置的节点要么都为空,要么都不为空;
    • 值相同:所有非空对应节点的 val 完全一致。
  3. 边界情况
    • 两棵树都为空 → 返回 true
    • 一棵为空一棵非空 → 返回 false

2、核心解法逻辑(最优:逐节点递归同步校验)

1. 核心思路

  • 递归终止锚点 :二叉树的叶子节点左右子节点都是 null,这是树的天然边界,必须以此作为终止条件(否则递归无限执行);
  • 校验优先级:先校验结构(节点是否为空),再校验值(避免空指针异常),最后递归校验子树;
  • 同步遍历特性 :不是传统前 / 中 / 后序遍历(单树收集值),而是 "双树同步深度优先校验 "------ 同步走两棵树的同一路径,逐节点核对,不匹配立刻终止

2. 执行流程(分步拆解)

  1. 终止条件 1 :若 p == null && q == null → 两棵树同步走到边界,结构匹配,返回 true
  2. 终止条件 2 :若 p == null || q == null → 结构不匹配(能走到这步说明不是都空),返回 false
  3. 值校验 :若 p.val != q.val → 对应节点值不同,返回 false
  4. 递归校验子树 :同步校验左子树 + 同步校验右子树(必须都匹配才返回 true,利用 && 短路特性提前终止)。

3. 为什么不选 "遍历存结果再对比"?

方案 时间复杂度 空间复杂度 逻辑缺陷 核心问题
逐节点递归校验 O(min(n,m)) O(min(h1,h2)) 不匹配立刻终止,效率最优
遍历(中/后序)对比 O(n+m) O(n+m) 遍历序列相同≠树结构相同 需完整遍历 + 额外存储,效率低

关键缺陷示例 :树 A(根 1→左 2)和树 B(根 2→右 1)的中序遍历结果都是 [2,1],但实际结构不同,遍历对比会误判。

3、标准模板代码

复制代码
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 1. 递归终止锚点:两棵树同步走到边界,结构匹配
        if (p == null && q == null) return true;
        // 2. 结构不匹配:一个空一个非空(已排除都空的情况)
        if (p == null || q == null) return false;
        // 3. 值不匹配:结构合法后再校验值,避免空指针
        if (p.val != q.val) return false;
        // 4. 同步递归校验子树:左子树和右子树必须都匹配(短路特性提前终止)
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

代码关键细节

  1. 终止条件顺序:必须先判断 "都空",再判断 "一空一非空",逻辑不可逆;
  2. 空指针防护 :先校验结构(是否为空),再访问 val,是新手避坑核心;
  3. 短路特性&& 左边为 false 时,右边递归不会执行,大幅减少无效遍历。

4、典型场景验证

场景 1:两棵树完全相同

复制代码
树p:        树q:
  1           1
 / \         / \
2   3       2   3
  • 执行流程:根节点匹配 → 左子节点 2 匹配 → 右子节点 3 匹配 → 返回 true

场景 2:结构相同但值不同

复制代码
树p:        树q:
  1           1
 / \         / \
2   3       2   4
  • 执行流程:根节点匹配 → 左子节点 2 匹配 → 右子节点 3≠4 → 返回 false(提前终止,无需遍历其他节点)。

场景 3:结构不同

复制代码
树p:        树q:
  1           1
 /             \
2               2
  • 执行流程:根节点匹配 → 校验左子树(p.left=2,q.left=null)→ 结构不匹配 → 返回 false

5、面试答题话术

我会用逐节点递归同步校验的方法解决这个问题,核心思路是:

  1. 以 "两个节点都为空" 作为递归终止锚点(树的天然边界,避免无限递归);
  2. 先校验结构(是否为空)再校验值(避免空指针),最后同步递归校验左右子树;
  3. 这个方法比 "遍历存结果再对比" 更优 ------ 利用短路特性提前终止,时间 / 空间复杂度更低,且能避免 "遍历序列相同但结构不同" 的误判。

总结

  1. 核心逻辑:递归终止锚点(都空返回 true)+ 先结构后值校验 + 同步子树校验,是最优解;
  2. 效率关键&& 短路特性,不匹配立刻终止,无需遍历全部节点;
  3. 避坑点:终止条件顺序不可调换,必须先判结构再判值,避免空指针异常。

三、另一棵树的子树

1、题目核心定义

  • 问题本质 :判断给定的 subRoot 树是否是 root 树的子树 (子树定义:从 root 树的某个节点开始,该节点及其所有后代节点构成的树与 subRoot 完全一致)。
  • 输入输出 :输入两棵二叉树的根节点 rootsubRoot,输出布尔值(true 表示是子树,false 表示不是);边界场景:若 subRoot 为空树,直接返回 true(空树是所有树的子树);若 root 为空且 subRoot 非空,返回 false

2、核心解法逻辑(递归)

整体思路

通过「遍历找候选根 + 校验子树一致性」两步实现:

  1. isSubtree 方法:递归遍历 root 树的所有节点,将每个节点作为 subRoot 的 "候选根";
  2. isSameTree 方法:校验某个 "候选根" 对应的子树是否与 subRoot 完全一致。

1. 核心方法:isSubtree(遍历找候选根)

复制代码
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    // 终止条件1:subRoot是空树,直接返回true(空树是所有树的子树)
    if (subRoot == null) return true;
    // 终止条件2:root是空树(且subRoot非空),返回false
    if (root == null) return false;
    // 递归逻辑:先找左子树的候选根 → 再找右子树的候选根 → 校验当前节点是否是匹配根
    return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot) || isSameTree(root, subRoot);
}
  • 执行流程
    1. 先递归 "递" 到 root 树的最深处(直到触达 null 节点);
    2. 再从最深处 "归" 回来,逐个节点验证是否是匹配根;
    3. || 是短路或:只要找到一个匹配的候选根,立即返回 true,无需遍历剩余节点。

2. 辅助方法:isSameTree(校验子树一致性)

复制代码
public boolean isSameTree(TreeNode root, TreeNode subRoot) {
    // 终止条件1:两者都为空,说明匹配
    if (root == null && subRoot == null) return true;
    // 终止条件2:其中一个为空、另一个非空,说明不匹配
    if (root == null || subRoot == null) return false;
    // 终止条件3:当前节点值不相等,说明不匹配
    if (root.val != subRoot.val) return false;
    // 递归逻辑:当前节点匹配后,需同时校验左、右子树都匹配
    return isSameTree(root.left, subRoot.left) && isSameTree(root.right, subRoot.right);
}
  • 执行流程
    1. 先校验当前节点是否匹配(值相等 + 非空状态一致);
    2. 再递归校验左子树、右子树是否都匹配;
    3. && 保证:只有 "根 + 左 + 右" 全匹配,才返回 true

3、关键细节解析

  1. 递归的 "递" 与 "归"
    • "递":isSubtree 会先遍历到 root 树的叶子节点(如节点 4),此时 isSubtree(4, 2) 内部三个条件都返回 false,最终给父节点(节点 2)返回 false
    • "归":回到父节点(节点 2)后,触发 isSameTree(2, 2),校验节点 2 及其子树是否与 subRoot 完全一致,匹配成功则返回 true
  2. 终止条件的作用
    • subRoot == null:仅处理 "subRoot 是空树" 这一种场景,且只在入口层生效(递归中 subRoot 参数不变);
    • root == null:是递归的 "刹车",避免无限递归,也是判断 "空树无法包含非空子树" 的核心。
  3. 方法分工
    • isSubtree:只负责 "找候选根",不关心子树是否匹配;
    • isSameTree:只负责 "校验匹配",不负责遍历节点。

4、复杂度分析

  • 时间复杂度O(n × m),其中 nroot 树的节点数,msubRoot 树的节点数。每个 root 节点都可能触发一次 isSameTree 校验(最多 n 次),每次校验最多遍历 m 个节点。
  • 空间复杂度O(max(h1, h2))h1root 树的高度,h2subRoot 树的高度。递归调用栈的深度取决于两棵树的最大高度,最坏情况(链状树)为 O(n)O(m)

5、核心结论

  1. 解题核心是「遍历 + 校验」:先通过 isSubtree 遍历所有节点找候选根,再通过 isSameTree 校验子树一致性;
  2. 递归的关键是 "先递后归":递到最深处排除错误节点,归到父节点验证正确节点;
  3. 短路逻辑(||/&&)是性能优化的关键,避免无效遍历和校验。
相关推荐
狐572 小时前
2026-01-20-LeetCode刷题笔记-3314-构造最小位运算数组I
笔记·算法·leetcode
FMRbpm2 小时前
树的练习7--------LCR 052.递增顺序搜索树
数据结构·c++·算法·leetcode·深度优先·新手入门
源代码•宸2 小时前
Golang原理剖析(GMP调度原理)
开发语言·经验分享·后端·面试·golang·gmp·runnext
技术民工之路2 小时前
MATLAB线性方程组,运算符、inv()、pinv()全解析
线性代数·算法·matlab
一起努力啊~2 小时前
算法刷题--双指针法
算法
麒qiqi2 小时前
ARM 学习笔记:从入门到理解嵌入式系统核心
arm开发·笔记·学习
Coovally AI模型快速验证2 小时前
从“单例模仿”到“多面融合”,视觉上下文学习迈向“团队协作”式提示融合
人工智能·学习·算法·yolo·计算机视觉·人机交互
hkNaruto2 小时前
【AI】AI学习笔记:翻译:langGraph 持久化执行 以及文档部分理解
笔记·学习·microsoft
pixcarp2 小时前
Golang web工作原理详解
开发语言·后端·学习·http·golang·web