117. 填充每个节点的下一个右侧节点指针 II
文章目录
- [117. 填充每个节点的下一个右侧节点指针 II](#117. 填充每个节点的下一个右侧节点指针 II)
-
- 题目描述
- [示例 1:](#示例 1:)
- [示例 2:](#示例 2:)
- 提示:
- 进阶:
- 问题深度分析
- 算法对比
- 算法流程图
- 复杂度分析
- 关键优化技巧
-
- [技巧一:虚拟头节点(Dummy Node)](#技巧一:虚拟头节点(Dummy Node))
- 技巧二:找到每层第一个节点
- [技巧三:BFS 层序遍历](#技巧三:BFS 层序遍历)
- 技巧四:递归处理空节点
- 边界情况处理
-
- [1. 空树](#1. 空树)
- [2. 单节点树](#2. 单节点树)
- [3. 每层最后一个节点](#3. 每层最后一个节点)
- [4. 节点缺失情况](#4. 节点缺失情况)
- [5. 层中所有节点都缺失子节点](#5. 层中所有节点都缺失子节点)
- 测试用例设计
- 常见错误与陷阱
- 实战技巧总结
-
- [1. 虚拟头节点的妙用](#1. 虚拟头节点的妙用)
- [2. 处理节点缺失的策略](#2. 处理节点缺失的策略)
- [3. 空间优化思路](#3. 空间优化思路)
- [4. 与116题的区别](#4. 与116题的区别)
- 进阶扩展
- 应用场景
-
- [1. 树形结构的横向遍历](#1. 树形结构的横向遍历)
- [2. 普通二叉树的层序操作](#2. 普通二叉树的层序操作)
- [3. 空间受限环境](#3. 空间受限环境)
- [4. 树形数据结构的序列化](#4. 树形数据结构的序列化)
- 相关题目
- 总结
- 完整题解代码
题目描述
给定一个二叉树:
C++
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
示例 1:

输入 :root = [1,2,3,4,5,null,7]
输出 :[1,#,2,3,#,4,5,7,#]
解释 :给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。
示例 2:
输入 :root = []
输出 :[]
提示:
- 树中的节点数在范围
[0, 6000]内 -100 <= Node.val <= 100
进阶:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序的隐式栈空间不计入额外空间复杂度。
问题深度分析
这是116题的扩展版本 ,核心区别在于普通二叉树 (节点可能缺失)与完美二叉树 (所有节点都存在)的处理差异。本题要求填充每个节点的 next 指针,使其指向同一层的下一个右侧节点,但树的结构不再保证完美,需要处理节点缺失的情况。
问题本质
给定一个普通二叉树,需要为每个节点建立指向同一层右侧相邻节点 的 next 指针。关键挑战:
- 节点可能缺失:左子树或右子树可能为空
- 层结构不规则:每层节点数不确定
- 跨子树连接复杂:需要找到下一个存在的节点
核心思想
处理节点缺失的情况:
- BFS方法:逐层处理,跳过空节点
- 递归方法:需要处理子树为空的情况
- 迭代O(1)方法:需要找到每层第一个非空节点,处理节点缺失
- 虚拟头节点:使用dummy节点简化连接逻辑
关键难点
- 找到每层第一个节点:普通二叉树中,每层第一个节点位置不确定
- 处理节点缺失:左子树或右子树为空时,连接逻辑需要调整
- 跨子树连接:需要跳过空节点找到下一个存在的节点
- 空间优化:在O(1)空间内处理不规则的树结构
算法对比
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 方法一:BFS层序遍历 | O(n) | O(w) | 直观易懂,需要队列存储 |
| 方法二:递归连接 | O(n) | O(h) | 利用递归栈,需要处理空节点 |
| 方法三:迭代 O(1) 空间(虚拟头节点) | O(n) | O(1) | 最优解,使用dummy简化逻辑 |
| 方法四:迭代 O(1) 空间(直接处理) | O(n) | O(1) | 不使用dummy,直接处理 |
说明:
n:节点总数w:树的最大宽度(最后一层节点数)h:树的高度
算法流程图
主算法流程
是
否
是
否
是
否
是
否
是
否
是
否
开始
根节点是否为空?
返回空
选择算法方法
方法一: BFS层序遍历
方法二: 递归连接
方法三: 迭代O1虚拟头节点
方法四: 迭代O1直接处理
创建队列
逐层处理节点
跳过空节点
连接同层相邻节点
处理下一层
是否还有层?
完成
处理当前节点
左子树存在?
连接左子树
跳过
右子树存在?
连接右子树
递归处理
利用父节点next找下一个
创建虚拟头节点dummy
从根节点开始逐层处理
使用dummy连接下一层
遍历当前层建立连接
移动到下一层
是否还有层?
找到每层第一个节点
遍历当前层
处理每个节点的子节点
找到下一个存在的节点
移动到下一层
是否还有层?
结束
方法三:虚拟头节点详细流程
是
否
是
否
是
否
是
否
开始: root节点
创建虚拟头节点dummy
初始化: curr = root
curr是否为空?
结束
初始化dummy.Next = nil
初始化tail = dummy
curr是否为空?
curr移动到下一层第一个节点
curr.Left存在?
tail.Next = curr.Left, tail = tail.Next
跳过
curr.Right存在?
tail.Next = curr.Right, tail = tail.Next
curr = curr.Next
curr = dummy.Next
返回root
节点缺失处理示意图
子节点层(有缺失)
父节点层
next
next
next
next
跳过
建立连接
建立连接
通过A.next建立
Node A
Node B
A.left
A.right
B.left
B.right
A.right缺失
复杂度分析
时间复杂度
所有方法均为 O(n):
- 每个节点被访问一次
- 每个节点的
next指针被设置一次 - 总操作次数与节点数成正比
空间复杂度
-
方法一(BFS):O(w)
- 队列最多存储一层的节点
- 普通二叉树最后一层节点数不确定
- 最坏情况
w = O(n)
-
方法二(递归):O(h)
- 递归调用栈深度等于树的高度
- 普通二叉树高度
h可能达到O(n) - 最坏情况
O(n)
-
方法三(迭代 O(1) + dummy):O(1)
- 只使用常数额外变量(dummy节点)
- dummy节点不算额外空间(题目允许)
-
方法四(迭代 O(1) 直接):O(1)
- 只使用常数额外变量
- 需要找到每层第一个节点
关键优化技巧
技巧一:虚拟头节点(Dummy Node)
核心思想:使用虚拟头节点简化连接逻辑,避免处理每层第一个节点的特殊情况。
go
// 方法三:迭代 O(1) 空间(虚拟头节点)
func connect3(root *Node) *Node {
if root == nil {
return nil
}
curr := root
for curr != nil {
// 创建虚拟头节点,用于连接下一层
dummy := &Node{}
tail := dummy
// 遍历当前层,连接下一层节点
for curr != nil {
if curr.Left != nil {
tail.Next = curr.Left
tail = tail.Next
}
if curr.Right != nil {
tail.Next = curr.Right
tail = tail.Next
}
curr = curr.Next
}
// 移动到下一层
curr = dummy.Next
}
return root
}
优势:
- 代码简洁,逻辑清晰
- 统一处理所有节点,无需特殊判断
- 空间复杂度 O(1)
技巧二:找到每层第一个节点
核心思想:在普通二叉树中,需要找到每层第一个非空节点作为起始点。
go
// 方法四:迭代 O(1) 空间(直接处理)
func connect4(root *Node) *Node {
if root == nil {
return nil
}
curr := root
// 逐层处理
for curr != nil {
// 找到当前层第一个有子节点的节点
var nextLevelFirst *Node
var nextLevelLast *Node
// 找到下一层第一个节点
for curr != nil {
if curr.Left != nil {
if nextLevelFirst == nil {
nextLevelFirst = curr.Left
nextLevelLast = curr.Left
} else {
nextLevelLast.Next = curr.Left
nextLevelLast = nextLevelLast.Next
}
}
if curr.Right != nil {
if nextLevelFirst == nil {
nextLevelFirst = curr.Right
nextLevelLast = curr.Right
} else {
nextLevelLast.Next = curr.Right
nextLevelLast = nextLevelLast.Next
}
}
curr = curr.Next
}
curr = nextLevelFirst
}
return root
}
优势:
- 不使用dummy节点
- 直接处理,逻辑直观
- 空间复杂度 O(1)
技巧三:BFS 层序遍历
核心思想:使用队列逐层处理,跳过空节点,为每层节点建立连接。
go
// 方法一:BFS层序遍历
func connect1(root *Node) *Node {
if root == nil {
return nil
}
queue := []*Node{root}
for len(queue) > 0 {
size := len(queue)
var prev *Node
for i := 0; i < size; i++ {
node := queue[0]
queue = queue[1:]
// 连接同层相邻节点
if prev != nil {
prev.Next = node
}
prev = node
// 添加子节点到队列(跳过空节点)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
}
return root
}
优势:
- 思路直观
- 适用于任意二叉树
- 易于理解和实现
技巧四:递归处理空节点
核心思想:递归处理时,需要处理子树为空的情况,利用父节点的next找到下一个节点。
go
// 方法二:递归连接
func connect2(root *Node) *Node {
if root == nil {
return nil
}
// 找到当前节点右侧第一个有子节点的节点
var next *Node
p := root.Next
for p != nil {
if p.Left != nil {
next = p.Left
break
}
if p.Right != nil {
next = p.Right
break
}
p = p.Next
}
// 连接当前节点的子节点
if root.Right != nil {
root.Right.Next = next
connect2(root.Right)
}
if root.Left != nil {
if root.Right != nil {
root.Left.Next = root.Right
} else {
root.Left.Next = next
}
connect2(root.Left)
}
return root
}
优势:
- 代码简洁
- 逻辑清晰
- 空间复杂度 O(h)
边界情况处理
1. 空树
go
if root == nil {
return nil
}
2. 单节点树
- 根节点的
next保持为NULL - 无需特殊处理
3. 每层最后一个节点
- 最后一个节点的
next应为NULL - BFS 方法中通过
prev指针处理
4. 节点缺失情况
- 左子树缺失:只处理右子树
- 右子树缺失:只处理左子树
- 左右子树都缺失:跳过,继续处理下一个节点
5. 层中所有节点都缺失子节点
- 该层为叶子节点层
- 迭代方法中
dummy.Next或nextLevelFirst为nil,循环结束
测试用例设计
基础测试
-
空树
- 输入:
[] - 输出:
[]
- 输入:
-
单节点
- 输入:
[1] - 输出:
[1,#]
- 输入:
-
两层树(完整)
- 输入:
[1,2,3] - 输出:
[1,#,2,3,#]
- 输入:
-
两层树(左子树缺失)
- 输入:
[1,null,3] - 输出:
[1,#,3,#]
- 输入:
标准测试
-
三层树(节点缺失)
- 输入:
[1,2,3,4,5,null,7] - 输出:
[1,#,2,3,#,4,5,7,#] - 验证:
- 节点2的next指向节点3
- 节点4的next指向节点5
- 节点5的next指向节点7
- 输入:
-
左偏树
- 输入:
[1,2,null,3,null,4] - 验证:每层节点正确连接
- 输入:
-
右偏树
- 输入:
[1,null,2,null,3,null,4] - 验证:每层节点正确连接
- 输入:
边界测试
-
完全不平衡树
- 测试极端不平衡情况
- 验证空间复杂度
-
所有节点只有左子树
- 输入:
[1,2,null,3,null,4] - 验证连接正确性
- 输入:
-
所有节点只有右子树
- 输入:
[1,null,2,null,3,null,4] - 验证连接正确性
- 输入:
常见错误与陷阱
错误一:未处理节点缺失
错误代码:
go
// 错误:假设所有节点都存在
curr.Left.Next = curr.Right
curr.Right.Next = curr.Next.Left
正确做法:
go
// 正确:检查节点是否存在
if curr.Left != nil {
if curr.Right != nil {
curr.Left.Next = curr.Right
} else {
// 需要找到下一个节点
}
}
错误二:未找到正确的下一个节点
错误代码:
go
// 错误:直接使用next的子节点,可能为空
if curr.Next != nil {
curr.Right.Next = curr.Next.Left // Left可能为nil
}
正确做法:
go
// 正确:找到下一个有子节点的节点
p := curr.Next
for p != nil {
if p.Left != nil {
curr.Right.Next = p.Left
break
}
if p.Right != nil {
curr.Right.Next = p.Right
break
}
p = p.Next
}
错误三:未找到每层第一个节点
错误代码:
go
// 错误:假设每层第一个节点在固定位置
leftmost = leftmost.Left // Left可能为nil
正确做法:
go
// 正确:找到下一层第一个非空节点
dummy := &Node{}
tail := dummy
// ... 连接下一层节点
curr = dummy.Next // 下一层第一个节点
错误四:BFS中队列大小变化
错误代码:
go
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
// 错误:队列大小在循环中变化
if len(queue) > 0 {
node.Next = queue[0]
}
}
正确做法:
go
for len(queue) > 0 {
size := len(queue) // 固定当前层大小
var prev *Node
for i := 0; i < size; i++ {
node := queue[0]
queue = queue[1:]
if prev != nil {
prev.Next = node
}
prev = node
}
}
实战技巧总结
1. 虚拟头节点的妙用
- 简化逻辑:统一处理所有节点,无需特殊判断
- 代码清晰:减少条件分支,提高可读性
- 空间优化:dummy节点不算额外空间
2. 处理节点缺失的策略
- 检查存在性:先检查节点是否存在再操作
- 跳过空节点:遍历时跳过空节点
- 找到下一个:需要找到下一个存在的节点
3. 空间优化思路
- 利用next指针:使用已建立的next指针遍历
- 逐层处理:处理完一层再处理下一层
- 虚拟头节点:使用dummy简化连接逻辑
4. 与116题的区别
- 116题(完美二叉树) :可以利用结构特性,直接访问
curr.Next.Left - 117题(普通二叉树):需要检查节点存在性,找到下一个存在的节点
进阶扩展
扩展一:N叉树版本
- 每个节点有多个子节点
- 需要连接所有相邻的子节点
- 算法需要相应调整
扩展二:从任意节点开始
- 给定树中某个节点
- 填充该节点所在层的所有
next指针 - 需要先找到该层的起始节点
扩展三:双向链表
- 不仅建立向右的
next指针 - 还建立向左的
prev指针 - 形成双向链表结构
扩展四:按Z字形连接
- 奇数层从左到右连接
- 偶数层从右到左连接
- 需要调整连接顺序
应用场景
1. 树形结构的横向遍历
- 场景:需要按层遍历树,但不想使用队列
- 优势 :利用
next指针可以直接遍历同一层
2. 普通二叉树的层序操作
- 场景:对普通二叉树进行层序相关操作
- 优势:处理节点缺失的情况
3. 空间受限环境
- 场景:内存有限,需要 O(1) 空间算法
- 优势:迭代方法满足空间要求
4. 树形数据结构的序列化
- 场景:需要按层序列化树结构
- 优势 :
next指针提供了层序信息
相关题目
- 116. 填充每个节点的下一个右侧节点指针:完美二叉树版本
- 102. 二叉树的层序遍历:层序遍历基础
- 103. 二叉树的锯齿形层序遍历:层序遍历变种
- 199. 二叉树的右视图:利用层序遍历
总结
本题是116题的扩展版本 ,核心在于处理普通二叉树中节点缺失的情况。通过四种不同的方法,我们展示了:
- BFS 方法:直观易懂,适用于理解问题
- 递归方法:代码简洁,需要处理空节点
- 迭代 O(1) + dummy:最优解,使用虚拟头节点简化逻辑
- 迭代 O(1) 直接:不使用dummy,直接处理
关键要点:
- 普通二叉树需要处理节点缺失的情况
- 虚拟头节点可以大大简化连接逻辑
- 需要找到每层第一个非空节点
- 跨子树连接需要找到下一个存在的节点
掌握本题有助于理解普通二叉树的层序遍历 和空间优化技巧 ,特别是虚拟头节点这一重要技巧,在链表和树的问题中都有广泛应用。
完整题解代码
go
package main
import (
"fmt"
)
// =========================== Node 定义 ===========================
type Node struct {
Val int
Left *Node
Right *Node
Next *Node
}
// =========================== 方法一:BFS层序遍历 ===========================
// 时间复杂度:O(n),空间复杂度:O(w),w为树的最大宽度
func connect1(root *Node) *Node {
if root == nil {
return nil
}
queue := []*Node{root}
for len(queue) > 0 {
size := len(queue)
var prev *Node
for i := 0; i < size; i++ {
node := queue[0]
queue = queue[1:]
// 连接同层相邻节点
if prev != nil {
prev.Next = node
}
prev = node
// 添加子节点到队列(跳过空节点)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
}
return root
}
// =========================== 方法二:递归连接 ===========================
// 时间复杂度:O(n),空间复杂度:O(h),h为树的高度
func connect2(root *Node) *Node {
if root == nil {
return nil
}
// 找到当前节点右侧第一个有子节点的节点
var next *Node
p := root.Next
for p != nil {
if p.Left != nil {
next = p.Left
break
}
if p.Right != nil {
next = p.Right
break
}
p = p.Next
}
// 连接当前节点的子节点(先处理右子树,再处理左子树)
if root.Right != nil {
root.Right.Next = next
connect2(root.Right)
}
if root.Left != nil {
if root.Right != nil {
root.Left.Next = root.Right
} else {
root.Left.Next = next
}
connect2(root.Left)
}
return root
}
// =========================== 方法三:迭代 O(1) 空间(虚拟头节点) ===========================
// 时间复杂度:O(n),空间复杂度:O(1)
func connect3(root *Node) *Node {
if root == nil {
return nil
}
curr := root
for curr != nil {
// 创建虚拟头节点,用于连接下一层
dummy := &Node{}
tail := dummy
// 遍历当前层,连接下一层节点
for curr != nil {
if curr.Left != nil {
tail.Next = curr.Left
tail = tail.Next
}
if curr.Right != nil {
tail.Next = curr.Right
tail = tail.Next
}
curr = curr.Next
}
// 移动到下一层
curr = dummy.Next
}
return root
}
// =========================== 方法四:迭代 O(1) 空间(直接处理) ===========================
// 时间复杂度:O(n),空间复杂度:O(1)
func connect4(root *Node) *Node {
if root == nil {
return nil
}
curr := root
// 逐层处理
for curr != nil {
// 找到下一层第一个节点和最后一个节点
var nextLevelFirst *Node
var nextLevelLast *Node
// 遍历当前层,建立下一层的连接
for curr != nil {
if curr.Left != nil {
if nextLevelFirst == nil {
nextLevelFirst = curr.Left
nextLevelLast = curr.Left
} else {
nextLevelLast.Next = curr.Left
nextLevelLast = nextLevelLast.Next
}
}
if curr.Right != nil {
if nextLevelFirst == nil {
nextLevelFirst = curr.Right
nextLevelLast = curr.Right
} else {
nextLevelLast.Next = curr.Right
nextLevelLast = nextLevelLast.Next
}
}
curr = curr.Next
}
// 移动到下一层
curr = nextLevelFirst
}
return root
}
// =========================== 工具函数:从数组构建二叉树 ===========================
func arrayToTreeLevelOrder(arr []interface{}) *Node {
if len(arr) == 0 || arr[0] == nil {
return nil
}
root := &Node{Val: arr[0].(int)}
queue := []*Node{root}
i := 1
for i < len(arr) && len(queue) > 0 {
node := queue[0]
queue = queue[1:]
// 左子节点
if i < len(arr) && arr[i] != nil {
left := &Node{Val: arr[i].(int)}
node.Left = left
queue = append(queue, left)
}
i++
// 右子节点
if i < len(arr) && arr[i] != nil {
right := &Node{Val: arr[i].(int)}
node.Right = right
queue = append(queue, right)
}
i++
}
return root
}
// =========================== 工具函数:按层序输出(带next信息) ===========================
func levelOrderWithNext(root *Node) []interface{} {
if root == nil {
return []interface{}{}
}
var result []interface{}
queue := []*Node{root}
for len(queue) > 0 {
size := len(queue)
for i := 0; i < size; i++ {
node := queue[0]
queue = queue[1:]
result = append(result, node.Val)
// 通过next指针找到同层下一个节点
if node.Next != nil {
// 继续处理同层节点
} else {
// 这一层结束了
}
// 添加子节点(用于下一层遍历)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
result = append(result, "#") // 层分隔符
}
return result
}
// =========================== 工具函数:通过next指针按层输出 ===========================
func levelOrderByNext(root *Node) []interface{} {
if root == nil {
return []interface{}{}
}
var result []interface{}
curr := root
// 找到每层第一个节点
for curr != nil {
// 找到当前层第一个节点
levelStart := curr
for levelStart != nil && levelStart.Left == nil && levelStart.Right == nil {
levelStart = levelStart.Next
}
if levelStart == nil {
break
}
// 通过next指针遍历当前层
p := levelStart
for p != nil {
result = append(result, p.Val)
p = p.Next
}
result = append(result, "#")
// 找到下一层第一个节点
nextLevelStart := (*Node)(nil)
p = levelStart
for p != nil {
if p.Left != nil {
nextLevelStart = p.Left
break
}
if p.Right != nil {
nextLevelStart = p.Right
break
}
p = p.Next
}
curr = nextLevelStart
}
return result
}
// =========================== 工具函数:验证next指针连接 ===========================
func validateNextPointers(root *Node) []interface{} {
if root == nil {
return []interface{}{}
}
var result []interface{}
queue := []*Node{root}
for len(queue) > 0 {
size := len(queue)
for i := 0; i < size; i++ {
node := queue[0]
queue = queue[1:]
result = append(result, node.Val)
// 添加子节点到队列
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
result = append(result, "#") // 层分隔符
}
return result
}
// =========================== 工具函数:比较结果 ===========================
func equal(a, b []interface{}) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// =========================== 测试 ===========================
func main() {
fmt.Println("=== LeetCode 117: 填充每个节点的下一个右侧节点指针 II ===\n")
testCases := []struct {
name string
root *Node
expected []interface{}
}{
{
name: "空树",
root: arrayToTreeLevelOrder([]interface{}{}),
expected: []interface{}{},
},
{
name: "单节点",
root: arrayToTreeLevelOrder([]interface{}{1}),
expected: []interface{}{1, "#"},
},
{
name: "两层树(完整)",
root: arrayToTreeLevelOrder([]interface{}{1, 2, 3}),
expected: []interface{}{1, "#", 2, 3, "#"},
},
{
name: "两层树(左子树缺失)",
root: arrayToTreeLevelOrder([]interface{}{1, nil, 3}),
expected: []interface{}{1, "#", 3, "#"},
},
{
name: "三层树(节点缺失)",
root: arrayToTreeLevelOrder([]interface{}{1, 2, 3, 4, 5, nil, 7}),
expected: []interface{}{1, "#", 2, 3, "#", 4, 5, 7, "#"},
},
{
name: "左偏树",
root: arrayToTreeLevelOrder([]interface{}{1, 2, nil, 3, nil, 4}),
expected: []interface{}{1, "#", 2, "#", 3, "#", 4, "#"},
},
{
name: "右偏树",
root: arrayToTreeLevelOrder([]interface{}{1, nil, 2, nil, 3, nil, 4}),
expected: []interface{}{1, "#", 2, "#", 3, "#", 4, "#"},
},
}
methods := []struct {
name string
fn func(*Node) *Node
}{
{"方法一:BFS层序遍历", connect1},
{"方法二:递归连接", connect2},
{"方法三:迭代O(1)空间(虚拟头节点)", connect3},
{"方法四:迭代O(1)空间(直接处理)", connect4},
}
allPassed := true
for _, method := range methods {
fmt.Printf("--- %s ---\n", method.name)
passed := 0
for i, tc := range testCases {
// 重新构建树(因为会修改原树)
testRoot := arrayToTreeLevelOrder(getTreeArray(tc.name))
result := method.fn(testRoot)
got := validateNextPointers(result)
if equal(got, tc.expected) {
fmt.Printf(" Test %d: ✓ PASSED\n", i+1)
passed++
} else {
fmt.Printf(" Test %d: ✗ FAILED\n", i+1)
fmt.Printf(" 输入: %v\n", getTreeArray(tc.name))
fmt.Printf(" 期望: %v\n", tc.expected)
fmt.Printf(" 得到: %v\n", got)
allPassed = false
}
}
fmt.Printf("通过率: %d/%d\n\n", passed, len(testCases))
}
if allPassed {
fmt.Println("🎉 所有测试通过!")
} else {
fmt.Println("❌ 部分测试失败")
}
}
// 辅助函数:根据测试用例名称获取树数组
func getTreeArray(name string) []interface{} {
switch name {
case "空树":
return []interface{}{}
case "单节点":
return []interface{}{1}
case "两层树(完整)":
return []interface{}{1, 2, 3}
case "两层树(左子树缺失)":
return []interface{}{1, nil, 3}
case "三层树(节点缺失)":
return []interface{}{1, 2, 3, 4, 5, nil, 7}
case "左偏树":
return []interface{}{1, 2, nil, 3, nil, 4}
case "右偏树":
return []interface{}{1, nil, 2, nil, 3, nil, 4}
default:
return []interface{}{}
}
}