2025.9.4
94. 二叉树的中序遍历
题目
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
提示:
树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
总体思路
迭代:栈模拟
中序遍历顺序:Left → Node → Right。
用栈来模拟递归:
- 用指针 cur 从根开始,一路把所有左孩子入栈,直到没有左孩子(cur == nil)。
- 弹栈得到当前节点 node,把它的值加入结果。
- 让 cur 指向 node.Right,然后继续 1~2 的循环。
- 终止条件:cur == nil 且 栈空。
时间复杂度 O(n),空间复杂度最坏 O(h)(树高,极端是 O(n))
递归实现
采用递归(Recursive)的方式:
如果节点为空,直接返回。
否则按照 左 → 根 → 右 的顺序访问节点,把结果存到切片 res 中。
代码
golang
迭代:栈模拟
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int // Val 存储节点的值(int 类型)
* Left *TreeNode // Left 指向左子节点。
* Right *TreeNode // Right 指向右子节点。
* }
*/
// 无注释纯享:
func inorderTraversal(root *TreeNode) []int {
res := make([]int, 0)
stack := make([]*TreeNode, 0)
cur := root
for cur !=nil || len(stack) > 0 {
for cur != nil {
stack = append(stack,cur)
cur=cur.Left
}
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
res = append (res, top.Val)
cur = top.Right
}
return res
}
// 迭代:栈模拟
// 中序遍历(迭代版,使用切片充当栈)
func inorderTraversal(root *TreeNode) []int {
// 结果切片:存遍历顺序
res := make([]int, 0)
// 用切片当栈,元素类型是 *TreeNode
stack := make([]*TreeNode, 0)
// cur 指向当前走到的节点
cur := root
// Go 没有 while,用 for 当 while:条件为“当前节点非空 或 栈非空”
for cur != nil || len(stack) > 0 {
// 1) 先把当前子树的“最左链”全部入栈
for cur != nil {
stack = append(stack, cur) // push
cur = cur.Left
}
// 2) 栈顶弹出一个节点
top := stack[len(stack)-1] // 取顶
stack = stack[:len(stack)-1] // 弹出
// 访问节点(中序的“根”)
res = append(res, top.Val)
// 3) 转向右子树,继续外层循环
cur = top.Right
}
return res
}
递归实现
// 无注释纯享:
func inorderTraversal(root *TreeNode) []int {
res := []int{}
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
if node == nil {
return
}
dfs(node.Left)
res = append(res, node.Val)
dfs(node.Right)
}
dfs(root)
return res
}
// 中序遍历(递归实现)
func inorderTraversal(root *TreeNode) []int {
// 定义一个切片保存遍历结果
res := []int{}
// 定义递归函数 dfs
var dfs func(*TreeNode)
dfs = func(node *TreeNode) {
if node == nil { // 递归终止条件:遇到空节点
return
}
// 1. 先递归遍历左子树
dfs(node.Left)
// 2. 访问当前节点,把节点值放入结果切片
res = append(res, node.Val)
// 3. 再递归遍历右子树
dfs(node.Right)
}
// 从根节点开始递归
dfs(root)
// 返回最终的遍历结果
return res
}
基础知识
中序遍历(Inorder Traversal)是二叉树遍历的一种方式,遍历顺序为:
遍历左子树->访问根节点->遍历右子树
定义了一个结构体 TreeNode,它代表二叉树的一个节点
Val 存储节点的值(int 类型)。
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
Left 指向左子节点。
Right 指向右子节点。
TreeNode 表示 一个结构体本身。
*TreeNode 表示 指向 TreeNode 的指针。
函数与递归
var dfs func(*TreeNode)
dfs = func(node *TreeNode) { ... }
var dfs func(*TreeNode)
声明了一个变量 dfs
,它的类型是函数,参数是 *TreeNode
(指向二叉树节点的指针),无返回值。
dfs = func(node *TreeNode) { ... }
定义了这个函数的具体内容。
这种写法属于 匿名函数赋值给变量,常用于递归或闭包。
dfs(node.Left) // 遍历左子树
dfs(node.Right) // 遍历右子树
104. 二叉树的最大深度
题目
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
输入:root = [1,null,2]
输出:2
提示:
树中节点的数量在 [0, 104] 区间内。
-100 <= Node.val <= 100
总体思路
递归
使用深度优先搜索 DFS的递归方法来计算二叉树的最大深度。其核心思想是:
- 递归地计算左子树的最大深度
- 递归地计算右子树的最大深度
- 取左右子树深度的最大值,然后加上当前节点自身的深度1
- 基础情况:当节点为空时,返回深度0
时间复杂度与空间复杂度
时间复杂度:O(n)
- 每个节点恰好被访问一次,n为二叉树中的节点数量
空间复杂度:O(h),其中h是树的高度
- 空间复杂度主要由递归调用栈的深度决定
- 在最坏情况下(树退化为链表),h = n,空间复杂度为O(n)
- 在最好情况下(平衡二叉树),h = logn,空间复杂度为O(logn)
- 平均情况下,空间复杂度为O(h)
深度优先搜索
迭代深度优先搜索(DFS) 来计算二叉树的最大深度。通过使用栈来模拟递归过程,显式地跟踪每个节点及其对应的深度,避免了递归调用的系统栈开销。
算法步骤
- 初始化:处理空树情况,初始化栈结构
- 根节点入栈:将根节点和初始深度1入栈
- 循环处理:不断从栈中弹出节点进行处理
- 深度更新:比较并更新最大深度
- 子节点入栈:将子节点和对应深度入栈
- 返回结果:栈空时返回最大深度
时间复杂度与空间复杂度
- 时间复杂度:O(n)
- 每个节点都被访问 exactly once
- n 是树中的节点数量
- 每个节点的入栈和出栈操作都是O(1)
- 空间复杂度:O(n)
- 最坏情况下栈的大小等于树的高度
- 平衡二叉树:O(log n)
- 链状二叉树(最坏情况):O(n)
- 平均情况:O(log n)
广度优先搜索
使用**广度优先搜索(BFS)**的方法来计算二叉树的最大深度。其核心思想是:
- 从根节点开始,按层级遍历树的每个节点
- 使用队列来存储待处理的节点及其对应的深度
- 每次处理一个节点时,如果当前深度大于记录的最大深度,就更新最大深度
- 将当前节点的子节点(如果存在)加入队列,并标记为下一层深度
- 当队列为空时,说明所有节点都已处理完毕,返回记录的最大深度
时间复杂度与空间复杂度
时间复杂度:O(n)
- 每个节点恰好被访问一次,n为二叉树中的节点数量
空间复杂度:O(n)
- 在最坏情况下(完全二叉树),队列中最多会存储约n/2个节点
- 在最好情况下(链状树),队列中最多会存储1个节点
- 平均情况下,空间复杂度为O(n)
代码
golang
递归法
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
return max(maxDepth(root.Left),maxDepth(root.Right)) + 1
}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
// 基础情况:如果当前节点为空,返回深度0
// 这是递归的终止条件
if root == nil {
return 0
}
// 递归计算左子树的最大深度
leftDepth := maxDepth(root.Left)
// 递归计算右子树的最大深度
rightDepth := maxDepth(root.Right)
// 返回左右子树深度的最大值 + 1(当前节点自身的深度)
// 这里使用了max函数(需要额外定义)
return max(leftDepth, rightDepth) + 1
}
// 辅助函数:返回两个整数中的较大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
深度优先搜索
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
depth := 0
stack := []struct{
node *TreeNode
level int
}{}
stack = append (stack, struct{node *TreeNode; level int}{root, 1})
for len(stack) > 0 {
temp := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if temp.level > depth {
depth = temp.level
}
if temp.node.Right != nil {
stack = append(stack, struct{node *TreeNode; level int}{temp.node.Right, temp.level + 1})
}
if temp.node.Left != nil {
stack = append(stack, struct{node *TreeNode; level int}{temp.node.Left, temp.level + 1})
}
}
return depth
}
/**
* 计算二叉树的最大深度(迭代版本)
* 使用深度优先搜索(DFS)策略,通过栈实现迭代遍历
*
* @param root *TreeNode 二叉树的根节点
* @return int 树的最大深度
*/
func maxDepth(root *TreeNode) int {
// 边界条件处理:空树深度为0
if root == nil {
return 0
}
// 初始化最大深度变量
depth := 0
// 定义栈元素的结构体
// 每个栈元素包含一个树节点和该节点对应的深度
type stackItem struct {
node *TreeNode // 树节点
level int // 该节点所在的深度
}
// 初始化栈,使用切片模拟栈结构
// Go语言中没有内置栈结构,使用切片实现栈功能
stack := []stackItem{}
// 将根节点和初始深度1压入栈中
// 根节点深度为1,因为深度从1开始计数
stack = append(stack, stackItem{root, 1})
// 主循环:当栈不为空时继续处理
// 循环条件:len(stack) > 0
for len(stack) > 0 {
// 弹出栈顶元素(后进先出LIFO)
// 获取栈顶元素
temp := stack[len(stack)-1]
// 移除栈顶元素(切片截取)
stack = stack[:len(stack)-1]
// 更新最大深度
// 比较当前节点的深度和已知最大深度,取较大值
if temp.level > depth {
depth = temp.level
}
// 处理右子节点(先压入右子节点,后处理)
// 因为栈是LIFO结构,先压入的后弹出
// 所以先压入右子节点,保证左子节点先被处理
if temp.node.Right != nil {
// 右子节点深度 = 当前节点深度 + 1
stack = append(stack, stackItem{temp.node.Right, temp.level + 1})
}
// 处理左子节点(后压入左子节点,先处理)
if temp.node.Left != nil {
// 左子节点深度 = 当前节点深度 + 1
stack = append(stack, stackItem{temp.node.Left, temp.level + 1})
}
}
// 返回计算得到的最大深度
return depth
}
广度优先搜索
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
depth := 0
queue := []struct{
node *TreeNode
level int
}{}
queue = append(queue, struct{node *TreeNode; level int}{root, 1})
for len(queue) > 0 {
temp := queue[0]
queue = queue[1:]
if temp.level > depth {
depth = temp.level
}
if temp.node.Left != nil {
queue = append(queue, struct{node *TreeNode; level int}{temp.node.Left, temp.level + 1})
}
if temp.node.Right != nil {
queue = append(queue, struct{node *TreeNode; level int}{temp.node.Right, temp.level + 1})
}
}
return depth
}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
// 如果根节点为空,直接返回深度0
if root == nil {
return 0
}
// 初始化最大深度
depth := 0
// 定义队列,使用结构体存储节点和对应的层级
// 这里使用了匿名结构体:包含节点指针和层级信息
queue := []struct{
node *TreeNode // 树节点
level int // 该节点所在的深度层级
}{}
// 将根节点和初始深度1加入队列
queue = append(queue, struct{node *TreeNode; level int}{root, 1})
// 当队列不为空时循环处理
for len(queue) > 0 {
// 从队列头部取出一个元素(先进先出)
temp := queue[0]
queue = queue[1:] // 移除队列头部元素
// 如果当前节点的层级大于记录的最大深度,更新最大深度
if temp.level > depth {
depth = temp.level
}
// 如果当前节点有左子节点,将其加入队列
// 子节点的深度为当前节点深度+1
if temp.node.Left != nil {
queue = append(queue, struct{node *TreeNode; level int}{temp.node.Left, temp.level + 1})
}
// 如果当前节点有右子节点,将其加入队列
// 子节点的深度为当前节点深度+1
if temp.node.Right != nil {
queue = append(queue, struct{node *TreeNode; level int}{temp.node.Right, temp.level + 1})
}
}
// 返回计算得到的最大深度
return depth
}