☰
二叉树展开为链表的三种解法与优化
2026/10/28 13:51:43 网站建设 项目流程

1. 二叉树展开为链表问题解析

这道力扣热门题目要求我们将二叉树原地展开为一个单链表,展开后的单链表应该与二叉树的前序遍历顺序相同。这个问题看似简单,但实际实现时需要特别注意指针操作和遍历顺序。

1.1 问题核心理解

题目给出的二叉树节点定义如下:

class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right

展开后的链表应该使用TreeNode的right指针作为next指针,left指针全部置为None。例如:

1 / \ 2 5 / \ \ 3 4 6 展开为: 1 \ 2 \ 3 \ 4 \ 5 \ 6

1.2 前序遍历特性分析

前序遍历的顺序是:根节点 → 左子树 → 右子树。要实现展开为链表,我们需要确保:

  1. 当前节点的right指针指向下一个前序遍历的节点
  2. 左子树的所有节点都出现在右子树之前
  3. 整个操作需要原地完成,不能使用额外空间

2. 解决方案设计与实现

2.1 递归解法思路

最直观的解法是使用递归前序遍历,记录前一个节点:

class Solution: def flatten(self, root: TreeNode) -> None: if not root: return # 保存右子树 right = root.right # 处理左子树 self.flatten(root.left) # 将处理好的左子树接到右子树位置 if root.left: root.right = root.left root.left = None # 找到新右子树的末尾 while root.right: root = root.right # 处理原来的右子树 self.flatten(right) root.right = right

注意:这种方法虽然直观,但时间复杂度是O(n^2),因为每次都需要遍历到左子树的末尾。

2.2 迭代解法优化

更高效的解法是使用迭代方式,利用栈模拟前序遍历:

class Solution: def flatten(self, root: TreeNode) -> None: if not root: return stack = [root] prev = None while stack: curr = stack.pop() if prev: prev.right = curr prev.left = None if curr.right: stack.append(curr.right) if curr.left: stack.append(curr.left) prev = curr

这种方法的时间复杂度是O(n),空间复杂度也是O(n)(栈空间)。

2.3 O(1)空间复杂度解法

最优解法可以达到O(1)空间复杂度,利用Morris遍历的思想:

class Solution: def flatten(self, root: TreeNode) -> None: curr = root while curr: if curr.left: # 找到左子树的最右节点 predecessor = curr.left while predecessor.right: predecessor = predecessor.right # 将右子树接到左子树的最右节点 predecessor.right = curr.right # 将左子树移到右子树位置 curr.right = curr.left curr.left = None curr = curr.right

这种方法不需要额外空间,时间复杂度仍然是O(n),是最优解法。

3. 关键点与注意事项

3.1 指针操作陷阱

在操作指针时需要特别注意顺序,常见的错误包括:

  1. 过早修改right指针导致丢失子树
  2. 忘记将left指针置为None
  3. 循环条件设置不当导致无限循环

3.2 测试用例设计

验证解法时需要覆盖以下情况:

  1. 空树
  2. 只有左子树的树
  3. 只有右子树的树
  4. 完全二叉树
  5. 退化成链表的树

3.3 复杂度分析对比

方法时间复杂度空间复杂度适用场景
递归O(n^2)O(n)教学理解
迭代O(n)O(n)一般使用
MorrisO(n)O(1)最优解

4. 实际应用与扩展

4.1 文件系统扁平化

这个算法可以应用于文件系统的扁平化操作,将树形目录结构转换为扁平列表。

4.2 数据库查询优化

在某些数据库查询优化中,需要将层次结构数据转换为线性结构以提高查询效率。

4.3 变种问题

  1. 按中序/后序遍历顺序展开
  2. 展开为双向链表
  3. 展开时不破坏原树结构(非原地操作)

5. 常见问题解答

Q: 为什么递归解法时间复杂度是O(n^2)?

A: 因为在处理每个节点时,可能需要遍历其左子树的所有右节点来找到末尾,最坏情况下(左子树退化成链表)会有O(n)的额外操作,总共n个节点,所以是O(n^2)。

Q: Morris解法中为什么不需要栈?

A: Morris解法利用了树中空闲的指针(叶子节点的right指针)来存储信息,通过修改这些指针临时保存遍历顺序,从而避免了栈的使用。

Q: 如何验证展开结果是否正确?

A: 可以通过以下步骤验证:

  1. 检查所有left指针是否为None
  2. 按照right指针遍历,记录节点值序列
  3. 对原树进行前序遍历,比较两个序列是否一致

Q: 如果要求展开为双向链表该如何修改?

A: 需要维护一个prev指针,在遍历时设置:

curr.left = prev if prev: prev.right = curr prev = curr

6. 刷题建议

  1. 先理解前序遍历的递归和迭代实现
  2. 画图分析指针变化过程
  3. 从简单递归解法开始,逐步优化
  4. 多测试边界条件
  5. 尝试不同的遍历顺序展开

这个题目很好地考察了对二叉树遍历和指针操作的理解,是力扣热题100中非常经典的题目,建议熟练掌握每种解法及其适用场景。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询