vault backup: 2026-05-17 14:27:46
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
---
|
||||
tags: ["LeetCode", "二叉树", "深度优先搜索", "迭代", "简单"]
|
||||
create time: 2026-05-17 10:00
|
||||
---
|
||||
|
||||
# 36-二叉树的中序遍历
|
||||
|
||||
## 题面
|
||||
|
||||
给定一个二叉树的根节点 `root`,返回它的 **中序** 遍历。
|
||||
|
||||
> 中序遍历的顺序:**左子树 → 根节点 → 右子树**。
|
||||
|
||||
**示例 1:**
|
||||
|
||||
```
|
||||
输入:root = [1,null,2,3]
|
||||
输出:[1,3,2]
|
||||
```
|
||||
|
||||
```
|
||||
1 遍历顺序
|
||||
\ ┌─────────┐
|
||||
2 ──→ │ 1 → 3 → 2│
|
||||
/ └─────────┘
|
||||
3
|
||||
```
|
||||
|
||||
**示例 2:**
|
||||
|
||||
```
|
||||
输入:root = []
|
||||
输出:[]
|
||||
```
|
||||
|
||||
**示例 3:**
|
||||
|
||||
```
|
||||
输入:root = [1]
|
||||
输出:[1]
|
||||
```
|
||||
|
||||
**约束:**
|
||||
|
||||
- 树中节点数目在范围 `[0, 100]` 内
|
||||
- `-100 <= Node.val <= 100`
|
||||
|
||||
**进阶:** 递归算法很简单,你可以通过迭代算法完成吗?
|
||||
|
||||
---
|
||||
|
||||
## 思路
|
||||
|
||||
> [!question] 💡 思考
|
||||
> 中序遍历的核心是"先深入左子树到底,再处理根,最后转向右子树"。递归版本天然利用系统调用栈来记住回溯路径——那如果我们手动模拟这个过程,该用什么数据结构?
|
||||
|
||||
### 方法一:递归(最直观)⭐
|
||||
|
||||
中序遍历的递归实现几乎是对定义的字面翻译:
|
||||
|
||||
```
|
||||
inorder(node):
|
||||
if node == nil: return
|
||||
inorder(node.left) // ① 先走左边
|
||||
visit(node.val) // ② 再取当前值
|
||||
inorder(node.right) // ③ 最后走右边
|
||||
```
|
||||
|
||||
这种写法简洁优雅,但进阶要求我们尝试迭代。让我们看看如何用显式栈来模拟递归的调用栈行为。
|
||||
|
||||
### 方法二:显式栈模拟(迭代)⭐⭐
|
||||
|
||||
#### 为什么用栈?
|
||||
|
||||
递归过程中,每进入一个节点的左子树,系统会把这个节点的"后续工作(访问自己、再访问右子树)"压入调用栈。当我们从左子树底部返回时,栈顶弹出刚才那个节点,进行处理。
|
||||
|
||||
**关键观察**:迭代的本质就是手动维护这个栈。
|
||||
|
||||
#### 核心流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["curr = root"] --> B{"curr != nil?"}
|
||||
B -->|"否"| C{"栈为空?"}
|
||||
C -->|"是"| D["结束, 返回结果"]
|
||||
C -->|"否"| E["弹栈得到 node"]
|
||||
E --> F["记录 node.val"]
|
||||
F --> G["curr = node.right"]
|
||||
G --> B
|
||||
B -->|"是"| H["一路向左\n每次经过的节点都压栈\ncurr = curr.left"]
|
||||
H --> B
|
||||
```
|
||||
|
||||
#### 逐步解析
|
||||
|
||||
以这棵树为例:
|
||||
|
||||
```
|
||||
2
|
||||
/ \
|
||||
1 3
|
||||
```
|
||||
|
||||
| 步骤 | 动作 | 栈 (底→顶) | curr | 结果 |
|
||||
|------|------|------------|------|------|
|
||||
| 初始 | — | `[]` | `2` | `[]` |
|
||||
| 1 | 2 压栈,走到左 | `[2]` | `1` | `[]` |
|
||||
| 2 | 1 压栈,走到左 | `[2, 1]` | `nil` | `[]` |
|
||||
| 3 | curr=nil,弹栈 `1` | `[2]` | `nil` | `[1]` |
|
||||
| 4 | 1 无右子树,curr=`nil` | `[2]` | `nil` | `[1]` |
|
||||
| 5 | curr=nil,弹栈 `2` | `[]` | `2` | `[1, 2]` |
|
||||
| 6 | 2 有右子树 3,curr=`3` | `[]` | `3` | `[1, 2]` |
|
||||
| 7 | 3 压栈,走到左 | `[3]` | `nil` | `[1, 2]` |
|
||||
| 8 | curr=nil,弹栈 `3` | `[]` | `nil` | `[1, 2, 3] ✅` |
|
||||
|
||||
**算法骨架**(记忆口诀:「能左就左,左到不能左就回退,回头往右拐」):
|
||||
|
||||
```
|
||||
curr = root
|
||||
while curr != nil 或 栈非空:
|
||||
// 阶段1: 一路向左,全部压栈
|
||||
while curr != nil:
|
||||
push(curr)
|
||||
curr = curr.left
|
||||
|
||||
// 阶段2: 栈顶就是下一个要访问的节点
|
||||
node = pop()
|
||||
result.append(node.val)
|
||||
|
||||
// 阶段3: 转向右子树
|
||||
curr = node.right
|
||||
```
|
||||
|
||||
> [!info] 🧠 为什么这个循环是正确的?
|
||||
> 想象你在走迷宫:看到岔路就往左边走,同时把走过的路口记下来(压栈)。当发现左边没路了,就回到上一个路口(弹栈),记录它,然后尝试右边的路。重复直到所有路口都走完且栈为空。
|
||||
|
||||
#### Go 语言实现细节
|
||||
|
||||
- Go 标准库没有内置栈,用 `[]T` 切片即可,追加和删除尾部操作均摊 O(1)
|
||||
- `nil` 判断直接 `node == nil`,不需要额外的判空辅助函数
|
||||
|
||||
---
|
||||
|
||||
## 代码提示
|
||||
|
||||
```
|
||||
// 伪代码模板
|
||||
初始化空栈 stack
|
||||
curr = root
|
||||
result = []
|
||||
|
||||
while curr != nil 或 len(stack) > 0:
|
||||
// 一路向左
|
||||
while curr != nil:
|
||||
stack = append(stack, curr)
|
||||
curr = curr.left
|
||||
|
||||
// 回退 + 记录
|
||||
node = stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
result = append(result, node.val)
|
||||
|
||||
// 转向右子树
|
||||
curr = node.right
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技巧
|
||||
|
||||
> [!tip] 🔑 核心模式:Morris 中序遍历(进阶面试杀手锏)
|
||||
> 上面的迭代法使用了 O(h) 空间(h 为树高)。如果面试官要求 **O(1)** 空间呢?答案是利用叶子节点的 nil 指针建立临时线索(Threaded Binary Tree),遍历完再恢复原状。虽然实际工程中很少用到,但在面试中是展示深度的利器。
|
||||
>
|
||||
> 基本思路:对于当前节点,找到其左子树的最右节点(前驱)。如果前驱的 right 指向 nil,说明第一次到达此节点,建立线索并往左走;如果前驱的 right 已经指向当前节点,说明左子树已遍历完毕,断开线索、记录当前值、往右走。
|
||||
|
||||
> [!note] 🐹 Go 切片栈操作速查
|
||||
> - 压栈:`stack = append(stack, val)`
|
||||
> - 弹栈:`val := stack[len(stack)-1]; stack = stack[:len(stack)-1]`
|
||||
> - 判空:`len(stack) == 0`
|
||||
> - 不要使用 `stack = nil` 代替缩容——这会丢失容量信息,影响后续 append 性能
|
||||
|
||||
> [!info] 📊 复杂度分析
|
||||
> - 时间:**O(n)**,每个节点恰好被压栈一次、弹栈一次,总共 2n 次栈操作
|
||||
> - 空间:**O(h)**,h 为树的高度。最坏情况(链状树)O(n),平均情况(平衡树)O(log n)
|
||||
|
||||
> [!comparison] ⚖️ 三种写法对比
|
||||
> | 维度 | 递归 | 迭代(显式栈) | Morris |
|
||||
> |------|------|--------------|--------|
|
||||
> | 代码简洁度 | ⭐⭐⭐ | ⭐⭐ | ⭐ |
|
||||
> | 空间复杂度 | O(h) 隐式栈 | O(h) 显式栈 | **O(1)** |
|
||||
> | 是否修改树结构 | ❌ | ❌ | 临时修改后恢复 |
|
||||
> | 面试推荐度 | 入门可用 | **首选** | 加分项 |
|
||||
|
||||
---
|
||||
|
||||
## 代码
|
||||
|
||||
### 迭代法(推荐)⭐⭐
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
func inorderTraversal(root *TreeNode) []int {
|
||||
var result []int // 存放遍历结果
|
||||
var stack []*TreeNode // 显式栈,存储待回退处理的节点
|
||||
curr := root // 工作指针
|
||||
|
||||
for curr != nil || len(stack) > 0 {
|
||||
// 阶段1: 一路向左,途经节点全部压栈
|
||||
for curr != nil {
|
||||
stack = append(stack, curr)
|
||||
curr = curr.Left
|
||||
}
|
||||
|
||||
// 阶段2: 栈顶元素即为下一个应访问的节点
|
||||
node := stack[len(stack)-1] // peek 栈顶
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
result = append(result, node.Val) // 记录值
|
||||
|
||||
// 阶段3: 转向右子树,下一轮继续从右子树最左开始
|
||||
curr = node.Right
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
### 递归法(参考)⭐
|
||||
|
||||
```go
|
||||
func inorderTraversal(root *TreeNode) []int {
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 左 → 根 → 右
|
||||
left := inorderTraversal(root.Left)
|
||||
right := inorderTraversal(root.Right)
|
||||
|
||||
// 拼接结果
|
||||
result := left
|
||||
result = append(result, root.Val)
|
||||
result = append(result, right...)
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
> [!success] ✅ 运行验证
|
||||
> 这是 LeetCode 第 94 题(编号更正:原题编号应为 94,此处笔记沿用用户原始编号 36),通过率约 70%,是中序遍历的经典入门题。迭代解法是理解二叉树遍历底层机制的基石——掌握后,前序/后序遍历的迭代写法只需调整「记录时机」即可迁移。
|
||||
@@ -0,0 +1,319 @@
|
||||
---
|
||||
tags: ["LeetCode", "二叉树", "深度优先搜索", "广度优先搜索", "递归", "简单"]
|
||||
create time: 2026-05-17 10:30
|
||||
---
|
||||
|
||||
# 37-二叉树的最大深度
|
||||
|
||||
## 题面
|
||||
|
||||
给定一个二叉树 `root`,返回其**最大深度**。
|
||||
|
||||
二叉树的 **最大深度** 是指从根节点到最远叶子节点的最长路径上的节点数。
|
||||
|
||||
> [!info] 💡 核心概念:什么是「深度」?
|
||||
> - 根节点的深度为 **1**
|
||||
> - 每往下一层,深度 **+1**
|
||||
> - 「最大深度」= 从根到**最远叶子节点**经过的节点总数
|
||||
>
|
||||
> > [!question] 🤔 思考:深度和高度有什么区别?
|
||||
> > - **深度(Depth)**:从根节点到当前节点的距离(自顶向下计数)
|
||||
> > - **高度(Height)**:从当前节点到最远叶子节点的距离(自底向上计数)
|
||||
> > - 根节点的高度 = 整棵树的最大深度,这是一个重要等价关系!
|
||||
|
||||
**示例 1:**
|
||||
|
||||
```
|
||||
输入:root = [3,9,20,null,null,15,7]
|
||||
输出:3
|
||||
解释:
|
||||
3 深度为 1
|
||||
/ \ ┌─────────────┐
|
||||
9 20 │ 最长路径:3→20→15 或 3→20→7 │
|
||||
│ 共 3 个节点,故深度为 3 │
|
||||
/ \ └─────────────┘
|
||||
15 7
|
||||
```
|
||||
|
||||
**示例 2:**
|
||||
|
||||
```
|
||||
输入:root = [1,null,2]
|
||||
输出:2
|
||||
解释:树退化为链状 1→2,深度为 2
|
||||
```
|
||||
|
||||
**约束:**
|
||||
|
||||
- 树中节点的数量在 `[0, 10^4]` 区间内
|
||||
- `-100 <= Node.val <= 100`
|
||||
|
||||
---
|
||||
|
||||
## 思路
|
||||
|
||||
### 方法一:深度优先搜索 DFS(递归)⭐⭐
|
||||
|
||||
#### 核心洞察
|
||||
|
||||
> [!question] 💡 思考
|
||||
> 对于任意一棵子树,它的最大深度等于什么?假设你已经知道左子树深度是 `L`、右子树深度是 `R`,那么这棵树的深度是多少?
|
||||
|
||||
答案一目了然:**`max(L, R) + 1`**。这个 `+1` 就是当前根节点自身。
|
||||
|
||||
这是典型的**自底向上**计算:先求出左右子树的答案,再合并得到当前结果。
|
||||
|
||||
#### 递归三要素
|
||||
|
||||
| 要素 | 内容 |
|
||||
|------|------|
|
||||
| **终止条件** | 节点为空时,深度为 0 |
|
||||
| **递归表达式** | `depth(node) = max(depth(node.left), depth(node.right)) + 1` |
|
||||
| **返回值** | 以 `node` 为根的树的深度 |
|
||||
|
||||
#### 执行流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["maxDepth(3)"] --> B["maxDepth(9)"]
|
||||
B --> C["maxDepth(nil) → 0"]
|
||||
B --> D["maxDepth(nil) → 0"]
|
||||
B --> E["max(0,0)+1 = 1 ✅"]
|
||||
A --> F["maxDepth(20)"]
|
||||
F --> G["maxDepth(15)"]
|
||||
G --> H["maxDepth(nil) → 0"]
|
||||
G --> I["maxDepth(nil) → 0"]
|
||||
G --> J["max(0,0)+1 = 1 ✅"]
|
||||
F --> K["maxDepth(7)"]
|
||||
K --> L["maxDepth(nil) → 0"]
|
||||
K --> M["maxDepth(nil) → 0"]
|
||||
K --> N["max(0,0)+1 = 1 ✅"]
|
||||
F --> O["max(1,1)+1 = 2 ✅"]
|
||||
A --> P["max(1,2)+1 = 3 ✅"]
|
||||
|
||||
style E fill:#d4edda
|
||||
style N fill:#d4edgreen
|
||||
style O fill:#cce5ff
|
||||
style P fill:#f8d7da,stroke:#721c24
|
||||
```
|
||||
|
||||
> [!note] 🧠 递归的执行视角
|
||||
> 想象一下:每个调用帧都在等待子调用的返回值,就像每个人都在等下属报业绩后再决定自己的绩效。直到遇到空节点(叶子之下),返回值逐层冒泡上来,层层累加 `+1`,最终算出整棵树的深度。
|
||||
|
||||
#### 为什么正确?
|
||||
|
||||
利用数学归纳法可以证明:
|
||||
|
||||
- **基准情况**:空树深度为 0 ✅
|
||||
- **归纳步骤**:假设所有高度小于 `h` 的子树都能正确返回深度,那么高度为 `h` 的树取其左右子树深度的最大值再加 1,显然也正确 ✅
|
||||
|
||||
### 方法二:广度优先搜索 BFS(层序遍历)⭐
|
||||
|
||||
除了「自底向上」的递归思路,还可以「自顶向下」逐层扫描——这正是 BFS 的拿手好戏。
|
||||
|
||||
#### 核心思想
|
||||
|
||||
BFS 天然按层访问节点。**遍历了多少层,树的深度就是多少。**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["层1: [3]"] -->|"深度 = 1"| B["层2: [9, 20]"]
|
||||
B -->|"深度 = 2"| C["层3: [15, 7]"]
|
||||
C -->|"深度 = 3"| D["无更多层\n返回 3 ✅"]
|
||||
|
||||
style A fill:#e8f5e9
|
||||
style B fill:#fff3e0
|
||||
style C fill:#e3f2fd
|
||||
style D fill:#fce4ec
|
||||
```
|
||||
|
||||
#### 算法骨架
|
||||
|
||||
```
|
||||
初始化队列 queue,放入 root
|
||||
depth = 0
|
||||
|
||||
while queue 非空:
|
||||
depth++ // 又扫完了一层
|
||||
n = queue.length // 固定当前层的节点数
|
||||
|
||||
for i from 0 to n-1: // 只处理当前层的节点
|
||||
node = queue.pop()
|
||||
if node.left != nil:
|
||||
push queue, node.left
|
||||
if node.right != nil:
|
||||
push queue, node.right
|
||||
|
||||
return depth
|
||||
```
|
||||
|
||||
> [!warning] ⚠️ 关键细节
|
||||
> 每一层开始前必须先记录 `n = queue.length`!如果直接在 `for` 循环里判断 `len(queue) > 0`,会因为子节点不断入队而导致死循环。
|
||||
|
||||
#### DFS vs BFS 如何选择?
|
||||
|
||||
> [!comparison] ⚖️ 深度优先 vs 广度优先
|
||||
> | 维度 | DFS(递归) | BFS(层序遍历) |
|
||||
> |------|------------|----------------|
|
||||
> | 代码简洁度 | ⭐⭐⭐ 仅需 3 行核心逻辑 | ⭐⭐ 需维护队列和层计数器 |
|
||||
> | 空间复杂度 | O(h),递归栈深度 | O(w),队列最多存一层宽度 |
|
||||
> | 实际效率 | 递归有函数调用开销 | 迭代,无调用开销 |
|
||||
> | 适用场景 | 通用解法,面试首选 | 需要按层处理问题时自然得出 |
|
||||
|
||||
> [!tip] 🔑 何时选哪种?
|
||||
> - 如果只问最大深度:**DFS 更简洁**,直接 return max(left, right) + 1
|
||||
> - 如果问「每层最大值」「锯齿形遍历」等按层相关的问题:**BFS 天然适合**
|
||||
> - 面试中两者都可以,DFS 通常更高效因为不需要额外维护队列结构
|
||||
|
||||
---
|
||||
|
||||
## 代码提示
|
||||
|
||||
### DFS 伪代码
|
||||
|
||||
```
|
||||
func maxDepth(node):
|
||||
if node == nil:
|
||||
return 0
|
||||
|
||||
left = maxDepth(node.left) // 左子树深度
|
||||
right = maxDepth(node.right) // 右子树深度
|
||||
|
||||
return max(left, right) + 1 // 取较大者 + 当前节点
|
||||
```
|
||||
|
||||
### BFS 伪代码
|
||||
|
||||
```
|
||||
if root == nil: return 0
|
||||
|
||||
queue = [root]
|
||||
depth = 0
|
||||
|
||||
while len(queue) > 0:
|
||||
depth++
|
||||
n = len(queue)
|
||||
|
||||
for i from 0 to n-1: // 只处理当前层的节点
|
||||
node = pop queue
|
||||
if node.left != nil:
|
||||
push queue, node.left
|
||||
if node.right != nil:
|
||||
push queue, node.right
|
||||
|
||||
return depth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技巧
|
||||
|
||||
> [!tip] 🔑 最大深度 = 最大高度(根节点视角)
|
||||
> 这道题本质上求的是「根节点的高度」。反过来想,**最小深度**则是「根到最近叶子节点」的距离。注意最小深度要求叶子节点(左右子树都为空),不能把有一侧子树为空的情况也算作叶子。
|
||||
|
||||
> [!tip] 🔑 一句代码搞定(Go 语言)
|
||||
> ```go
|
||||
> func maxDepth(root *TreeNode) int {
|
||||
> if root == nil { return 0 }
|
||||
> return imax(maxDepth(root.Left), maxDepth(root.Right)) + 1
|
||||
> }
|
||||
> ```
|
||||
> Go 标准库没有 `max` 泛函数(截至 1.21),需要自己写一个辅助函数或用 `if` 判断。Go 1.21+ 的 `slices.Max` 仅适用于切片,不适用于两个标量比较。
|
||||
|
||||
> [!note] 📊 复杂度速查
|
||||
> | 方法 | 时间复杂度 | 空间复杂度 |
|
||||
> |------|-----------|-----------|
|
||||
> | DFS 递归 | **O(n)** — 每个节点访问一次 | **O(h)** — h 为树高,最坏 O(n)(链状),平均 O(log n)(平衡树) |
|
||||
> | BFS 迭代 | **O(n)** — 每个节点入队出队各一次 | **O(w)** — w 为树的最大宽度,最坏 O(n)(满二叉树最后一层) |
|
||||
|
||||
> [!info] 🧩 延伸思考
|
||||
> 如果把「最大深度」改成「直径」(任意两节点间的最长路径),思路会如何变化?答案是在每个节点同时记录「深度」和「经过该节点的最长路径」,用全局变量实时更新最大值。见 LeetCode 543. 二叉树的直径。
|
||||
|
||||
---
|
||||
|
||||
## 代码
|
||||
|
||||
### DFS 递归法(推荐)⭐⭐
|
||||
|
||||
```go
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
// 递归求左右子树的深度
|
||||
left := maxDepth(root.Left) // 左子树深度
|
||||
right := maxDepth(root.Right) // 右子树深度
|
||||
|
||||
// 当前节点深度 = 较深子树 + 1
|
||||
if left > right {
|
||||
return left + 1
|
||||
}
|
||||
return right + 1
|
||||
}
|
||||
|
||||
// 辅助函数:求两个整数的较大值
|
||||
// Go 1.21 以下没有内置 max(int, int),需自行实现
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
```
|
||||
|
||||
> [!success] ✅ 运行验证
|
||||
> 这是 LeetCode 第 104 题,通过率约 73%。作为二叉树入门第一题,它完美展示了递归的「分而治之」思想:**将大问题拆解为相同形式的子问题**,直到触及最简单的边界(空节点)。
|
||||
|
||||
### BFS 层序遍历法(参考)⭐
|
||||
|
||||
```go
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
var queue []*TreeNode // 使用切片模拟队列
|
||||
queue = append(queue, root)
|
||||
depth := 0
|
||||
|
||||
for len(queue) > 0 {
|
||||
n := len(queue) // 固定当前层的节点数量
|
||||
depth++ // 遍历了一层,深度 +1
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
node := queue[0] // 出队
|
||||
queue = queue[1:] // 切片弹出首部
|
||||
|
||||
if node.Left != nil {
|
||||
queue = append(queue, node.Left) // 左孩子入队
|
||||
}
|
||||
if node.Right != nil {
|
||||
queue = append(queue, node.Right) // 右孩子入队
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return depth
|
||||
}
|
||||
```
|
||||
|
||||
> [!note] 🐹 Go 队列优化提示
|
||||
> 上述 BFS 使用 `queue = queue[1:]` 出队,但切片头部不断删除会导致底层数组无法释放,内存只增不减。在实际工程中可以用双指针(head/tail 索引)避免缩容;但在这道题的数据范围(≤ 10^4 节点)下,两种写法差异可忽略,教学场景以最清晰为准。
|
||||
Reference in New Issue
Block a user