# 114. 二叉树展开为链表
# 题目描述
给你二叉树的根结点 root,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode,其中right子指针指向链表中下一个结点,而左子指针始终为null。 - 展开后的单链表应该与二叉树 前序遍历 顺序相同。
示例 1:
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]
2
示例 2:
输入:root = []
输出:[]
2
示例 3:
输入:root = [0]
输出:[0]
2
提示:
- 树中结点数在范围
[0, 2000]内 -100 <= Node.val <= 100
进阶: 你可以使用原地算法(O(1) 额外空间)展开这棵树吗?
# 思路
题目要求按前序遍历顺序展开成链表,所以首先要理解前序遍历的顺序:根→左→右。
# 方法一:先遍历再重组(额外空间)
最直观的思路:
- 前序遍历树,把所有节点存到一个数组中
- 遍历数组,把每个节点的
left设为null,right指向下一个节点
这个方法简单易懂,但空间复杂度 O(n),不符合进阶要求。
# 方法二:递归原地展开(O(1)空间)
能不能在遍历的同时重组指针,不用额外空间呢?
关键是理解递归的本质:递归处理左右子树,然后把它们"拼接"到根节点后面。
具体步骤:
- 递归展开左子树和右子树
- 此时左子树已经是一条链,右子树也是一条链
- 把左子树的链接到根节点的
right - 找到左子树链的末尾,把右子树链接在后面
- 把根节点的
left设为null
# 方法三:前序遍历+当场改指针(O(1)空间,最优)
还有一个更巧妙的方法:从右往左,从下往上处理。
用一个变量 prev 记录前序遍历中"上一个访问的节点",然后按右→左→根的顺序递归(前序遍历的倒序):
- 先递归处理右子树
- 再递归处理左子树
- 最后处理根节点:把当前节点的
right指向prev,left设为null
为什么是倒序?因为我们要让每个节点的 right 指向"前序遍历的下一个节点",倒着处理就能用 prev 记录这个"下一个节点"。
# 模拟过程
以 root = [1,2,5,3,4,null,6] 为例:
1
/ \
2 5
/ \ \
3 4 6
2
3
4
5
前序遍历顺序:1 → 2 → 3 → 4 → 5 → 6
方法二(递归拼接)的过程:
第一步:递归处理左子树(节点2)
- 递归展开节点2的左子树(节点3)和右子树(节点4)
- 把节点3接到节点2的right
- 把节点4接到节点3的right
- 左子树展开完成:
2 → 3 → 4
第二步:递归处理右子树(节点5)
- 递归展开节点5的右子树(节点6)
- 右子树展开完成:
5 → 6
第三步:拼接到根节点
- 把左子树链
2→3→4接到节点1的right - 找到左子树链的末尾(节点4),把右子树链
5→6接在后面 - 最终:
1 → 2 → 3 → 4 → 5 → 6
drawio 提示词: 画两张图展示递归拼接过程。第一张展示原始树结构,标注前序遍历顺序 1→2→3→4→5→6,用不同颜色区分左子树和右子树。第二张展示拼接过程的三个阶段:左子树递归展开、右子树递归展开、拼接到根节点,用箭头表示指针的重组,最终形成一条右链。两张图画布统一,Logo 放左下角。
文件名:
0114.flatten-binary-tree-01-structure.drawio0114.flatten-binary-tree-02-recursion-process.drawio
# 解题代码
# 方法二:递归拼接
class Solution {
public:
void flatten(TreeNode* root) {
if (root == nullptr) return;
// 递归展开左右子树
flatten(root->left);
flatten(root->right);
// 保存右子树
TreeNode* rightTree = root->right;
// 把左子树移到右边
root->right = root->left;
root->left = nullptr;
// 找到当前右子树(原左子树)的末尾
TreeNode* curr = root;
while (curr->right != nullptr) {
curr = curr->right;
}
// 把原来的右子树接在末尾
curr->right = rightTree;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 方法三:倒序递归(最优)
class Solution {
private:
TreeNode* prev = nullptr; // 记录前序遍历的上一个节点
public:
void flatten(TreeNode* root) {
if (root == nullptr) return;
// 倒序:右→左→根
flatten(root->right);
flatten(root->left);
// 重组当前节点
root->right = prev;
root->left = nullptr;
prev = root; // 更新prev
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 复杂度分析
- 时间复杂度:O(n),每个节点访问一次
- 空间复杂度:O(h),递归栈的深度,h 是树的高度
# 其他语言
# Python3
class Solution:
def flatten(self, root: Optional[TreeNode]) -> None:
if not root:
return
# 递归展开左右子树
self.flatten(root.left)
self.flatten(root.right)
# 保存右子树
right_tree = root.right
# 把左子树移到右边
root.right = root.left
root.left = None
# 找到当前右子树的末尾
curr = root
while curr.right:
curr = curr.right
# 把原来的右子树接在末尾
curr.right = right_tree
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Java
class Solution {
public void flatten(TreeNode root) {
if (root == null) return;
// 递归展开左右子树
flatten(root.left);
flatten(root.right);
// 保存右子树
TreeNode rightTree = root.right;
// 把左子树移到右边
root.right = root.left;
root.left = null;
// 找到当前右子树的末尾
TreeNode curr = root;
while (curr.right != null) {
curr = curr.right;
}
// 把原来的右子树接在末尾
curr.right = rightTree;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Go
func flatten(root *TreeNode) {
if root == nil {
return
}
// 递归展开左右子树
flatten(root.Left)
flatten(root.Right)
// 保存右子树
rightTree := root.Right
// 把左子树移到右边
root.Right = root.Left
root.Left = nil
// 找到当前右子树的末尾
curr := root
for curr.Right != nil {
curr = curr.Right
}
// 把原来的右子树接在末尾
curr.Right = rightTree
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# JavaScript
var flatten = function(root) {
if (!root) return;
// 递归展开左右子树
flatten(root.left);
flatten(root.right);
// 保存右子树
const rightTree = root.right;
// 把左子树移到右边
root.right = root.left;
root.left = null;
// 找到当前右子树的末尾
let curr = root;
while (curr.right) {
curr = curr.right;
}
// 把原来的右子树接在末尾
curr.right = rightTree;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 与代码随想录联系
这道题是二叉树指针重组的经典题目,考察的是递归思维和指针操作。
关键是理解"递归处理子树,然后拼接"的思路。这种思路在很多二叉树题目中都会用到,比如:
- 构造二叉树(通过前序中序/后序中序)
- 二叉树的序列化与反序列化
- 合并两棵二叉树
建议录友们先把代码随想录二叉树章节的基础递归题刷完,再来看这道题,会更容易理解。
另外,本题的"前序遍历"是关键线索。如果录友们对前序遍历还不熟悉,可以先看 94. 二叉树的中序遍历 和前序遍历的相关题目。
二叉树的很多题目都是"遍历+指针操作"的结合,掌握了递归遍历,再加上对指针的灵活操作,就能解决大部分二叉树题目了。
评论
验证登录状态...