

文章目录
摘要
这题把"把 N 叉树变成二叉树、再从二叉树恢复 N 叉树"作为练习,目的是考察你对树结构的灵活变换和对指针关系的把握。
核心思想是经典的"左子右兄弟"(left-child right-sibling)映射:把每个 N 叉节点的第一个子节点映射为二叉树的左子节点,把每个子节点的下一个兄弟映射为二叉树的右子节点。这样可以在不丢失结构信息的前提下,把任意 N 叉树表示为二叉树,反向变换也同样可行。
本文会给出完整的 Swift 实现(编码和解码),并讲清楚每一步为什么这么做,同时给出示例和验证代码,便于在 Playground/项目中直接运行。

描述
题目给出两种节点定义:
N 叉树节点:
swift
class Node {
var val: Int
var children: [Node]
init(_ val: Int) {
self.val = val
self.children = []
}
}
二叉树节点(用于编码后结果):
swift
class TreeNode {
var val: Int
var left: TreeNode?
var right: TreeNode?
init(_ val: Int) {
self.val = val
}
}
要求实现两个方法:
encode(_ root: Node?) -> TreeNode?:把 N 叉树编码为二叉树decode(_ root: TreeNode?) -> Node?:从二叉树还原 N 叉树
关键是保持节点值和结构信息的可逆性。
题解答案
经典的映射规则如下:
-
对于 N 叉树中某个节点
u,如果u.children非空,则:- 把
u在二叉树中对应的节点的left指向u.children[0]对应的二叉树节点(第一个子节点作为左孩子)。 - 对于
u.children[i],如果i+1 < children.count,则把u.children[i]在二叉树的对应节点的right指向u.children[i+1]在二叉树的对应节点(把兄弟节点用右指针串起来)。
- 把
-
其他指针保持 null(nil)。
反向变换自然就是:
- 对于二叉树中某个节点
t,把t.left代表的整条由right指针串起来的链表拆成 N 叉树的 children 数组(沿right指针遍历,依次 decode)。
这个映射是双向可逆的,且实现很直接,递归或迭代都可。
下面给出一个易读的 Swift 实现,并配套构造/打印函数,能在 Playground 中直接运行验证。

题解代码分析(可运行 Swift 示例)
swift
import Foundation
// MARK: - N 叉树节点定义
class Node {
var val: Int
var children: [Node]
init(_ val: Int) {
self.val = val
self.children = []
}
}
// MARK: - 二叉树节点定义(用于编码结果)
class TreeNode {
var val: Int
var left: TreeNode?
var right: TreeNode?
init(_ val: Int) {
self.val = val
}
}
// MARK: - Codec:encode / decode
class Codec {
// Encode: N 叉树 -> 二叉树
func encode(_ root: Node?) -> TreeNode? {
guard let root = root else { return nil }
// 当前 N 叉节点映射为二叉树节点
let bRoot = TreeNode(root.val)
// 如果有 children,第一个 child 变为 left
if !root.children.isEmpty {
bRoot.left = encode(root.children[0])
}
// 将 children 链接为右兄弟链表
var currentBChild = bRoot.left
var i = 1
while i < root.children.count {
currentBChild?.right = encode(root.children[i])
currentBChild = currentBChild?.right
i += 1
}
return bRoot
}
// Decode: 二叉树 -> N 叉树
func decode(_ root: TreeNode?) -> Node? {
guard let root = root else { return nil }
let nRoot = Node(root.val)
// left 指向第一个 child(如果有)
var bChild = root.left
while let b = bChild {
if let decodedChild = decode(b) {
nRoot.children.append(decodedChild)
}
// 兄弟通过 right 链接
bChild = b.right
}
return nRoot
}
}
// MARK: - 辅助函数:构造示例 N 叉树并演示 encode/decode
// 构造示例树:
// 1
// / | \
// 2 3 4
// / \
// 5 6
func buildExampleTree() -> Node {
let root = Node(1)
let n2 = Node(2)
let n3 = Node(3)
let n4 = Node(4)
let n5 = Node(5)
let n6 = Node(6)
root.children = [n2, n3, n4]
n3.children = [n5, n6]
return root
}
// 打印 N 叉树的层序(以便对比)
func levelOrderNary(_ root: Node?) -> [[Int]] {
guard let root = root else { return [] }
var result: [[Int]] = []
var queue: [Node] = [root]
while !queue.isEmpty {
let size = queue.count
var level: [Int] = []
for _ in 0..<size {
let node = queue.removeFirst()
level.append(node.val)
for child in node.children {
queue.append(child)
}
}
result.append(level)
}
return result
}
// 打印二叉树按层(方便查看编码结果)
func levelOrderBinary(_ root: TreeNode?) -> [[Int?]] {
guard let root = root else { return [] }
var result: [[Int?]] = []
var queue: [TreeNode?] = [root]
while !queue.isEmpty {
let size = queue.count
var level: [Int?] = []
for _ in 0..<size {
let node = queue.removeFirst()
if let n = node {
level.append(n.val)
// 右 child 作为同一层的 sibling,会被视作 separate node
// 但为了直观展示,我们仍把 left 和 right 加入队列。
queue.append(n.left)
queue.append(n.right)
} else {
level.append(nil)
}
}
// trim trailing nils for cleaner output
while let last = level.last, last == nil {
level.removeLast()
}
result.append(level)
}
// trim trailing empty levels
while let last = result.last, last.isEmpty {
result.removeLast()
}
return result
}
// MARK: - 运行示例
let codec = Codec()
let nRoot = buildExampleTree()
print("原 N 叉树层序:", levelOrderNary(nRoot))
let bRoot = codec.encode(nRoot)
print("编码后二叉树的层序(显示 nil 用于占位):", levelOrderBinary(bRoot))
let decoded = codec.decode(bRoot)
print("解码回 N 叉树层序:", levelOrderNary(decoded))
代码解析与要点
-
映射思想
N-Node->TreeNode(val)first child->lefteach child的right->next sibling(兄弟通过right串起来)
-
编码要点
- 先把当前节点创建成
TreeNode - 如果
root.children非空,先把bRoot.left = encode(root.children[0]) - 然后顺序把
children[1...]encode 并通过right连接 - 递归结束会把每个子树正确地放在
left/right的位置
- 先把当前节点创建成
-
解码要点
-
Decode 时对每个二叉树节点
root:- 生成
Node(root.val) - 沿
root.left再沿着right遍历(兄弟链),对这些二叉节点递归 decode 并 append 到 children 数组
- 生成
-
这样可以把
left指向的"孩子链表"变回 N 叉节点数组
-
-
双向可逆
- 以上编码/解码规则保证没有信息丢失:值和父子 / 兄弟结构都被保留
- encode(decode(encode(root))) 与 encode(root) 的形态一致,decode(encode(root)).children 与 root.children 等价
-
复杂度
- 时间复杂度:编码和解码都需要访问每个节点一次,因此都是 O(N),N 为 N 叉树节点数
- 空间复杂度:递归调用栈深度取决于树高,最坏 O(N),最好 O(log N) 取决结构(另外结果的二叉树或者临时队列也需要 O(N) 空间用于输出)
示例测试及结果(解释输出)
上面示例构造的 N 叉树是:
txt
1
/ | \
2 3 4
/ \
5 6
运行输出将是类似下面的三行(在 Playground 中直接打印):
- 原 N 叉树层序:
[[1], [2, 3, 4], [5, 6]] - 编码后二叉树的层序:例如
[[1], [2, 3], [nil, 5, nil, 6]](格式化显示里会展示 nil 占位,便于理解 left/right 位置) - 解码回 N 叉树层序:
[[1], [2, 3, 4], [5, 6]]
如果原始树和解码后树的层序输出一致,说明 encode/decode 正确。
时间复杂度
- 编码:每个 N 叉节点会被访问一次并且在递归中对其 children 调用 encode,总体 O(N)。
- 解码:同样每个二叉树节点被访问一次并且会进行常数时间的追加操作,总体 O(N)。
因此 encode 与 decode 都是线性时间。
空间复杂度
- 额外空间主要来自递归栈深度:最坏情况 O(N)(例如一个链式 N 叉树,所有节点都只有一个 child);
- 同时编码结果(二叉树)和解码需要构建新的节点或重用节点参考,这些本就是输出规模,因此总占用与输入输出规模线性相关。
总体额外空间(不计输出)在最坏情形为 O(N)。
总结
这道题的思想非常直观但很有技巧: left-child right-sibling(左子右兄)映射是把任意子数目结构化为固定 2 指针结构的标准方法。这个映射被广泛应用于树到二叉树转换、紧凑树表示等场景。
解决步骤总结:
- 想清楚映射规则(first child -> left,siblings -> right)
- 用递归实现 encode,把 children 链表沿 right 串起来
- 用递归实现 decode,把 left 指向的链表沿 right 拆回来作为 children 数组
- 编写辅助打印/测试用例验证结果