114. 二叉树展开为链表#
现实映射:圣诞灯饰的魔法#
想象你有一串圣诞树造型的彩灯,每个分岔处都有两个小灯泡。现在你想把它改造成一条直线悬挂在屋檐下。要求所有灯泡必须保持原来的点亮顺序,且只能用右侧的挂钩连接。这就是我们今天要解决的算法问题!
问题描述#
题目目标#
LeetCode第114题要求:给定一个二叉树的根节点,原地将它展开为一个单链表,展开后的链表顺序应与二叉树的前序遍历顺序一致。所有节点的右子指针指向下一个节点,左子指针始终为null。
示例 1#
输入:
1
/ \
2 5
/ \ \
3 4 6text输出:
1
\
2
\
3
\
4
\
5
\
6text说明: 把输入和输出放在一起看,会更容易直接抓住题目要求的变化。
补充说明#
- 二叉树通常使用层序数组表示,
null表示该位置没有节点。
直觉解法:前序遍历+重建#
最直接的思路就像拆解圣诞灯饰:
- 先完整记录所有灯泡的位置(前序遍历)
- 按照顺序重新组装成链条
Java实现#
class Solution {
public void flatten(TreeNode root) {
// 按前序遍历顺序保存所有节点,后续按顺序重连
List<TreeNode> list = new ArrayList<>();
// 收集前序遍历节点序列
preOrder(root, list);
// 依次把每个节点指向序列中的下一个节点
for (int i = 0; i < list.size()-1; i++) {
// 当前节点
TreeNode curr = list.get(i);
// 按题意展开后左指针必须为 null
curr.left = null;
// 右指针指向前序序列中的下一个节点
curr.right = list.get(i+1);
}
}
private void preOrder(TreeNode node, List<TreeNode> list) {
// 递归终止:空节点直接返回
if (node == null) return;
// 前序:先记录当前节点
list.add(node);
// 再遍历左子树
preOrder(node.left, list);
// 最后遍历右子树
preOrder(node.right, list);
}
}java复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)(递归栈+列表存储)
忍者解法:原地修改的奥义#
真正的忍者不需要额外空间!我们需要在遍历的同时完成链表重组,就像在拆解灯饰的过程中直接重新连接灯泡。
核心思想:寻找前驱节点#
在前序遍历中,每个节点的后继其实是其左子树的最右节点。抓住这个规律,我们可以:
-
当前节点有左子树时:
- 找到左子树的最右节点(前驱节点)
- 将当前节点的右子树接到前驱节点的右侧
- 将左子树移到右侧,左指针置空
-
移动到右子节点,重复上述过程
关键步骤演示(以示例为例)#
初始状态:
1
/ \
2 5
/ \ \
3 4 6
第1步:处理节点1
左子树最右节点是4
将5接到4的右侧:
1
/
2
/ \
3 4
\
5
\
6
然后左子树移到右侧:
1
\
2
/ \
3 4
\
5
\
6
第2步:处理节点2
同理操作后变为:
1
\
2
\
3
\
4
\
5
\
6plaintextJava实现#
class Solution {
public void flatten(TreeNode root) {
// curr 用于沿着“展开后的右链”一路向下处理
TreeNode curr = root;
// 遍历整棵树,直到没有后续节点
while (curr != null) {
// 只有存在左子树时才需要重组
if (curr.left != null) {
// predecessor 指向当前节点左子树的最右节点(前驱节点)
TreeNode predecessor = curr.left;
// 持续向右走到最右端
while (predecessor.right != null) {
predecessor = predecessor.right;
}
// 1) 把当前节点原右子树挂到前驱节点右侧
predecessor.right = curr.right;
// 2) 把当前节点左子树整体移动到右侧
curr.right = curr.left;
// 3) 按题意清空左指针
curr.left = null;
}
// 移动到下一个节点(始终沿右指针前进)
curr = curr.right;
}
}
}java复杂度分析
时间复杂度:O(n)(每个节点被访问两次)
空间复杂度:O(1)
解法对比#
| 方法 | 时间复杂度 | 空间复杂度 | 修改方式 |
|---|---|---|---|
| 前序遍历+重建 | O(n) | O(n) | 非原地 |
| 原地修改法 | O(n) | O(1) | 原地 |
模式总结#
这道题体现了两个重要算法思想:
- 莫里斯遍历思想:通过修改树结构来实现O(1)空间遍历
- 链表重组技巧:寻找前驱节点的操作模式
这种模式可以扩展到:
- 将二叉树展开为中序链表
- 将二叉搜索树转换为循环双向链表
- 其他需要原地修改树结构的问题
忍者心法#
真正的算法高手,就像优秀的工匠改造灯饰:
- 洞察结构:发现前驱节点的关键作用
- 精细操作:在遍历时同步完成结构调整
- 节约资源:用最少的空间完成最复杂的改造
记住:当题目要求原地修改时,往往需要找到当前结构的某种内在规律,通过巧妙的指针操作来达成目标。