Leetcode100: 101.对称二叉树、543.二叉树直径、102.层序遍历

题目1:对称二叉树(LeetCode 101)

问题描述

给你一个二叉树的根节点 root,检查它是否轴对称。即:左子树和右子树互为镜像。

示例:

复制代码
输入: root = [1, 2, 2, 3, 4, 4, 3]
输出: true
解释:
    1
   / \
  2   2
 / \ / \
3  4 4  3

解题思路

核心思想: 递归(双指针)

  • 编写一个辅助函数 isMirror(left, right),判断两棵树是否互为镜像

  • 互为镜像的条件:

    1. 两个节点都为 null → true

    2. 其中一个为 null → false

    3. 两个节点的值相等,且 left.leftright.right 互为镜像,left.rightright.left 互为镜像

Java代码(带详细注释)

java 复制代码
/**
 * LeetCode 101. 对称二叉树
 * 难度:简单
 * 给你一个二叉树的根节点 root,检查它是否轴对称。
 */
public class SymmetricTree {
    public boolean isSymmetric(TreeNode root) {
        // 空树是对称的
        if (root == null) {
            return true;
        }
        // 检查根节点的左右子树是否互为镜像
        return isMirror(root.left, root.right);
    }
    
    /**
     * 辅助方法:判断两棵树是否互为镜像
     */
    private boolean isMirror(TreeNode left, TreeNode right) {
        // 1. 两个节点都为空 → 对称
        if (left == null && right == null) {
            return true;
        }
        
        // 2. 其中一个为空 → 不对称
        if (left == null || right == null) {
            return false;
        }
        
        // 3. 值相等 且 左.left == 右.right 且 左.right == 右.left
        return left.val == right.val
            && isMirror(left.left, right.right)   // 左的左 和 右的右
            && isMirror(left.right, right.left);  // 左的右 和 右的左
    }
}

流程图(Mermaid)

执行过程图解:

复制代码
        1
       / \
      2   2
     / \ / \
    3  4 4  3

调用 isMirror(2, 2):

Step 1: left.val(2) == right.val(2) ✓
        递归1: isMirror(3, 3)
          ├─ left.val(3) == right.val(3) ✓
          ├─ 递归1.1: isMirror(null, null) → true
          ├─ 递归1.2: isMirror(null, null) → true
          └─ 返回 true
        递归2: isMirror(4, 4)
          ├─ left.val(4) == right.val(4) ✓
          ├─ 递归2.1: isMirror(null, null) → true
          ├─ 递归2.2: isMirror(null, null) → true
          └─ 返回 true
        返回 true && true = true

最终: true ✓

问题1:return isMirror(root.left, root.right); 为什么是return?

答: 是的!这行代码就是返回一个方法调用的结果

核心理解:

java 复制代码
public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return true;
    }
    // 这行代码的意思是:
    // 调用 isMirror() 方法,然后把它的返回值直接返回
    return isMirror(root.left, root.right);
}

拆解执行过程:

复制代码
Step 1: 调用 isMirror(root.left, root.right)
        ↓
Step 2: isMirror() 执行完毕,返回一个 boolean 值(true 或 false)
        ↓
Step 3: 把这个 boolean 值作为 isSymmetric() 的返回值

// 等价于:
boolean result = isMirror(root.left, root.right);
return result;

// 再等价于(分开写):
boolean result = isMirror(root.left, root.right);
if (result == true) {
    return true;
} else {
    return false;
}

为什么要这样写?

复制代码
因为 isSymmetric() 的返回值类型是 boolean
isMirror() 的返回值类型也是 boolean

所以 isMirror() 的返回值可以直接作为 isSymmetric() 的返回值!

就像:
int add(int a, int b) {
    return a + b;  // 返回计算结果
}

boolean isPositive(int x) {
    return x > 0;  // 返回判断结果(true/false)
}

题目2:二叉树的直径(LeetCode 543)

问题描述

给定一棵二叉树,计算它的直径长度。直径是任意两个节点路径长度中的最大值。这条路径可能经过也可能不经过根节点。

示例:

复制代码
输入: root = [1, 2, 3, 4, 5]
输出: 3
解释:
    1
   / \
  2   3
 / \
4   5
直径: 4-2-1-3 或 5-2-1-3,长度3

解题思路

核心思想: 递归 + 全局变量

  • 对于每个节点,计算:

    • leftDepth:左子树的最大深度

    • rightDepth:右子树的最大深度

    • 经过当前节点的路径长度 = leftDepth + rightDepth

  • 用全局变量 maxDiameter 记录所有节点中最大的路径长度

  • 返回当前节点的最大深度 = max(leftDepth, rightDepth) + 1

Java代码(带详细注释)

java 复制代码
/**
 * LeetCode 543. 二叉树的直径
 * 难度:简单
 * 给定一棵二叉树,计算它的直径长度。
 */
public class DiameterOfBinaryTree {
    // 全局变量,记录最大直径
    private int maxDiameter = 0;
    
    public int diameterOfBinaryTree(TreeNode root) {
        // 空树直径是0
        if (root == null) {
            return 0;
        }
        // 计算深度,同时更新直径
        calculateDepth(root);
        return maxDiameter;
    }
    
    /**
     * 辅助方法:计算树的深度,同时更新直径
     * 返回值:当前树的最大深度
     */
    private int calculateDepth(TreeNode node) {
        // 空节点深度为0
        if (node == null) {
            return 0;
        }
        
        // 计算左右子树的深度
        int leftDepth = calculateDepth(node.left);
        int rightDepth = calculateDepth(node.right);
        
        // 经过当前节点的路径长度 = 左深度 + 右深度
        // 更新全局最大直径
        maxDiameter = Math.max(maxDiameter, leftDepth + rightDepth);
        
        // 返回当前节点的深度(到叶子节点的最长路径)
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

流程图(Mermaid)

执行过程图解:

复制代码
        1
       / \
      2   3
     / \
    4   5

calculateDepth(4):
  leftDepth = 0, rightDepth = 0
  maxDiameter = max(0, 0+0) = 0
  返回 1

calculateDepth(5):
  leftDepth = 0, rightDepth = 0
  maxDiameter = max(0, 0+0) = 0
  返回 1

calculateDepth(2):
  leftDepth = calculateDepth(4) = 1
  rightDepth = calculateDepth(5) = 1
  maxDiameter = max(0, 1+1) = 2  ← 路径 4-2-5,长度2
  返回 max(1,1)+1 = 2

calculateDepth(3):
  leftDepth = 0, rightDepth = 0
  maxDiameter = max(2, 0+0) = 2
  返回 1

calculateDepth(1):
  leftDepth = calculateDepth(2) = 2
  rightDepth = calculateDepth(3) = 1
  maxDiameter = max(2, 2+1) = 3  ← 路径 4-2-1-3,长度3
  返回 max(2,1)+1 = 3

最终: maxDiameter = 3 ✓

关键理解:

复制代码
每个节点都可能是"路径的拐点"(最高点)

节点1作为拐点:路径长度 = 左深度(2) + 右深度(1) = 3
节点2作为拐点:路径长度 = 左深度(1) + 右深度(1) = 2

直径是经过所有节点中最大的那个路径!
所以用全局变量记录最大值。

题目3:二叉树的层序遍历(LeetCode 102)

问题描述

给你二叉树的根节点 root,返回其节点值的层序遍历。(即逐层地,从左到右访问所有节点)

示例:

复制代码
输入: root = [3, 9, 20, null, null, 15, 7]
输出: [[3], [9, 20], [15, 7]]
解释:
    3
   / \
  9  20
    /  \
   15   7

解题思路

核心思想: BFS(广度优先搜索)+ 队列

  • 使用队列存储当前层的所有节点

  • 每次处理一层:取出队列中所有节点,将它们的值加入当前层列表

  • 同时将它们的左右子节点加入队列,供下一层使用

  • 当前层处理完后,将列表加入结果

Java代码(带详细注释)

java 复制代码
import java.util.*;

/**
 * LeetCode 102. 二叉树的层序遍历
 * 难度:中等
 * 给你二叉树的根节点 root,返回其节点值的层序遍历。
 */
public class BinaryTreeLevelOrderTraversal {
    public List<List<Integer>> levelOrder(TreeNode root) {
        // 创建结果列表
        List<List<Integer>> result = new ArrayList<>();
        
        // 边界检查
        if (root == null) {
            return result;
        }
        
        // 使用队列存储节点(FIFO)
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);  // 根节点入队
        
        // 当队列不为空时,说明还有节点需要处理
        while (!queue.isEmpty()) {
            // 当前层的节点数量(队列中现有的节点数)
            int levelSize = queue.size();
            
            // 当前层的值列表
            List<Integer> levelValues = new ArrayList<>();
            
            // 处理当前层的所有节点
            for (int i = 0; i < levelSize; i++) {
                // 取出队首节点
                TreeNode node = queue.poll();
                
                // 将节点的值加入当前层列表
                levelValues.add(node.val);
                
                // 将子节点加入队列(供下一层处理)
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            
            // 当前层处理完毕,加入结果
            result.add(levelValues);
        }
        
        return result;
    }
}

流程图(Mermaid)

执行过程图解:

复制代码
        3
       / \
      9  20
        /  \
       15   7

队列操作过程:

第1层(根节点):
队列: [3]  ← 初始
levelSize = 1
处理: 取出3,加入层列表 [3]
      加入子节点: 9, 20
      队列变成: [9, 20]
结果: [[3]]

第2层(节点9和20):
队列: [9, 20]
levelSize = 2
处理节点9: 取出9,加入层列表 [9]
           子节点为空
处理节点20: 取出20,加入层列表 [9, 20]
            加入子节点: 15, 7
            队列变成: [15, 7]
结果: [[3], [9, 20]]

第3层(节点15和7):
队列: [15, 7]
levelSize = 2
处理节点15: 取出15,加入层列表 [15]
            子节点为空
处理节点7: 取出7,加入层列表 [15, 7]
           子节点为空
           队列变成: []
结果: [[3], [9, 20], [15, 7]]

最终: [[3], [9, 20], [15, 7]] ✓

为什么用队列?

复制代码
层序遍历 = 先进先出(BFS)

队列的特点:
先入队的节点先处理
左孩子先入队 → 左孩子先处理
右孩子后入队 → 右孩子后处理

所以能保证从左

问题1:Queue<TreeNode> queue = new LinkedList<>(); 编译器怎么理解的?

答: 这是Java的多态面向接口编程!我来梳理完整的编译和运行过程。

代码解析:

复制代码
Queue<TreeNode> queue = new LinkedList<>();
//    ↑接口        ↑实现类

编译阶段(静态类型检查):

复制代码
编译时:
编译器看到 queue 的类型是 Queue<TreeNode>
         ↓
编译器检查 LinkedList 是否是 Queue 的子类型
         ↓
是的!LinkedList 实现了 Queue 接口
         ↓
编译通过!

编译器只知道 queue 有 Queue 接口中定义的方法
(add, offer, poll, peek 等)

编译器不知道也不关心具体的实现类是 LinkedList

运行阶段(动态绑定):

复制代码
运行时:
queue 实际上指向了一个 LinkedList 对象

当调用 queue.offer(node) 时:
JVM 发现 queue 实际是 LinkedList
        ↓
执行 LinkedList 中的 offer 方法
        ↓
多态!运行时会执行实际对象的真实方法

问题2:队列有哪些方法?

答: Java的 Queue 接口提供了以下主要方法:

队列方法分类:

操作 抛出异常 返回特殊值 说明
插入 add(e) offer(e) 向队列尾部添加元素
移除 remove() poll() 从队列头部移除并返回元素
检查 element() peek() 返回队列头部元素但不移除

详细说明:

java 复制代码
Queue<Integer> queue = new LinkedList<>();

// 1. 插入元素(入队)
queue.offer(1);  // 返回 true,推荐使用
queue.add(2);    // 队列满时抛出异常

// 2. 移除元素(出队)
Integer head = queue.poll();  // 队列空时返回 null,推荐使用
Integer head2 = queue.remove(); // 队列空时抛出异常

// 3. 检查元素(不移除)
Integer peek = queue.peek();  // 队列空时返回 null,推荐使用
Integer peek2 = queue.element(); // 队列空时抛出异常

在层序遍历中用到的方法:

java 复制代码
Queue<TreeNode> queue = new LinkedList<>();

queue.offer(root);        // 入队:根节点入队

TreeNode node = queue.poll();  // 出队:取出队首节点

queue.isEmpty();  // 判断队列是否为空

完整示例:

java 复制代码
Queue<String> queue = new LinkedList<>();

// 入队
queue.offer("A");
queue.offer("B");
queue.offer("C");

System.out.println(queue);  // [A, B, C]

// 查看队首(不移除)
System.out.println(queue.peek());  // A
System.out.println(queue);  // [A, B, C](没变)

// 出队
System.out.println(queue.poll());  // A
System.out.println(queue);  // [B, C]

System.out.println(queue.poll());  // B
System.out.println(queue);  // [C]

System.out.println(queue.poll());  // C
System.out.println(queue);  // []

System.out.println(queue.poll());  // null(队列空了)
相关推荐
栈溢出了8 小时前
Java Lambda 表达式笔记
java·开发语言·intellij-idea
会周易的程序员8 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot
zhixingheyi_tian8 小时前
一份GC日志的解读
java·开发语言
ywl4708120878 小时前
ArrayList和LinkedList内部的实现原理和区别
java·java基础
沉静的小伙8 小时前
Spring事务
java·数据库·spring
apihz8 小时前
随机驾考题目(C 照科一 / 科四 2000+ 题)免费API调用教程
android·java·c语言·开发语言·网络协议·tcp/ip
AIGS0019 小时前
企业AI落地:Agent OS 治理框架实践
java·大数据·人工智能·机器学习·人工智能ai大模型应用
-西门吹雪10 小时前
现代C++ 并发编程-学习指南
java·jvm·c++
lh179310 小时前
数据产业服务分类(53)——分类设计——数据能力集成
java·人工智能·分类
想你依然心痛10 小时前
自定义RTOS内核:从零实现上下文切换与任务调度——汇编、PendSV
java·开发语言·汇编·pendsv