给你二叉树的根结点 root
,请你将它展开为一个单链表:
示例 1:
输入:root = [1,2,5,3,4,null,6] 输出:[1,null,2,null,3,null,4,null,5,null,6]
示例 2:
输入:root = [] 输出:[]
示例 3:
输入:root = [0] 输出:[0]
提示:
- 树中结点数在范围
[0, 2000]
内 -100 <= Node.val <= 100
进阶:你可以使用原地算法(O(1)
额外空间)展开这棵树吗?
步骤1:问题定义与分析
问题性质:
给定一个二叉树的根节点 root
,需要将二叉树“展开”为一个单链表,要求:
输入条件:
- 二叉树节点个数
n
的范围是[0, 2000]
。 - 树节点值的范围是
[-100, 100]
。
输出条件: 返回一个以二叉树先序遍历顺序展开的链表。
限制与边界条件:
- 空树:
root = []
应返回[]
。 - 单节点树:
root = [0]
应直接返回[0]
。 - 完全二叉树或非完全二叉树,均需考虑遍历顺序正确性。
- 要求实现原地算法(
O(1)
空间复杂度)。
步骤2:算法设计
- 遍历树的所有节点,按照先序遍历(根 -> 左 -> 右)顺序重新连接
right
指针。 - 在不使用额外数据结构(如栈)的情况下,原地展开,保持
O(1)
空间复杂度。
算法步骤:
-
使用递归法(基础版本):
- 通过递归实现先序遍历,并将节点按顺序重新连接。
- 时间复杂度:
O(n)
,每个节点访问一次。 - 空间复杂度:
O(h)
,h
为树的高度(递归栈消耗),最差为O(n)
。
-
优化为 Morris 遍历法(原地实现):
- 利用 Morris 遍历的思想,避免使用额外空间。
- 每次找到当前节点左子树的最右节点,将其
right
指针指向当前节点的右子树,然后移动到下一个节点。 - 时间复杂度:
O(n)
。 - 空间复杂度:
O(1)
。
步骤3:代码实现
以下是基于 Morris 遍历 的 C++ 实现
步骤4:优化与启发
-
算法优化的思考:
- Morris 遍历充分利用了树的空闲指针,不依赖额外栈空间,将空间复杂度降到
O(1)
。 - 时间复杂度保持为
O(n)
,对每个节点的访问次数为常数级。
- Morris 遍历充分利用了树的空闲指针,不依赖额外栈空间,将空间复杂度降到
-
启发:
- 在解决树的问题时,可以通过改变节点连接关系而减少空间消耗。
- Morris 遍历的思想广泛适用于中序、前序遍历的优化。
-
处理大规模数据:
- 由于时间复杂度为线性,且空间复杂度为常数,该算法能够高效处理节点数达 2000 的二叉树。
- 对于深度极大的树(如链式树结构),递归法可能引发栈溢出,而 Morris 遍历能够规避这一问题。
步骤5:实际应用场景
应用场景: 该算法的思想可以应用于以下场景:
具体实例: 在文件系统中,将目录结构表示为二叉树。为了遍历所有文件(先序顺序),可以利用此算法原地将目录树展开为链表,并顺序访问所有文件路径,无需占用额外的栈或队列。
实现方法:
以上解决方案既考虑了算法设计的高效性,也注重实际应用的可行性和通用性。