二叉树展开为链表:先记下旧右,再接左
压成只向右的链,顺序必须等于前序。左子树最右节点是接缝:旧右先挂上去,再把左子树挪到右侧,左指针置空。顺序反了会丢节点。
对每个还有左孩子的节点 n:先沿左子树一路向右找到前序末尾 t,把 n 的旧右子树接到 t.Right,再执行 n.Right = n.Left、n.Left = nil。然后沿右链继续。先改 n.Right 会丢掉旧右子树。时间 O(n),空间 O(1)。
给你一棵二叉树的根,把它原地展开成单链表:每个节点只保留右孩子,左孩子一律为空。链表里的节点顺序必须等于这棵树的前序遍历:根、整棵左、整棵右。
主例层序 [1, 2, 5, 3, 4]:根 1,左子树是 2(下面再挂 3 和 4),右孩子是 5。前序是 1、2、3、4、5,展开后必须是 1→2→3→4→5,而且全部走右指针。
新建一条链再抄一遍前序能做对,但题目要原地改指针。本文要回答:前序的接缝究竟在哪个节点,以及为什么必须先挂上旧右、再把左子树接到自己右边。
接缝在左子树最右,旧右必须先挂上
前序把一棵子树拆成三段:根自己、整棵左、整棵右。展开之后这三段必须首尾相接。根的下一个一定是左子树的第一个节点;左子树走完,下一个必须是右子树的第一个节点。所以真正要找的不是「整棵树的前序」,而是左子树里最后一个被前序访问的节点——它就是两段的接缝。
一棵已经展开、或尚未展开但只看右链的子树,前序最后一个节点就是一路向右走到头的那个。对当前节点 n,从 n.Left 出发,不停走 Right,停在没有右孩子的节点 t 上。t 就是左子树的前序末尾。主例的 n=1,左子树根是 2,2 的右孩子是 4,4 没有右孩子,t 就是 4。
缺的不是这个位置,而是改指针的顺序。n 同时握着左子树和右子树。若先写 n.Right = n.Left,原来的右子树立刻没人指着,5 整棵丢掉。正确顺序只有一种:先把旧右接到 t 上,t.Right = n.Right;这时 4 已经抓住 5,再把左子树移到右侧,n.Right = n.Left,最后 n.Left = nil。三步缺一不可,前两步不能对调。
用手走主例。n=1,有左子树 [2,3,4]。从 2 向右走到 4,这是演示第一、二帧。第三帧把 5 接到 4 的右边,再把 1 的右指针改向 2、左指针清空。此刻形状是 1→2,2 仍保留左孩子 3、右孩子 4,4 已经连着 5。树还没平,但 5 已经安全挂在接缝上,不会丢。
沿右链走到 2。2 还有左孩子 3。3 没有右孩子,自己就是左子树前序末尾。先把 2 的旧右(4,后面跟着 5)接到 3 上,再把 3 移到 2 的右侧,2 的左指针置空。现在是 1→2→3→4→5。3、4、5 都没有左孩子,指针一路右移到空,整棵压平。演示最后一帧说的「2 的左子树末尾 3 接 4」,就是这一步。
没有左孩子的节点什么都不用改,直接看右孩子。空树、单节点、本来就是右链的树,循环零次或只前进、不改接缝。空间始终是几个指针,不需要递归栈。
为什么不能先改 n.Rightn.Right 是旧右子树唯一的入口。先把它改成左子树,旧右立刻不可达。必须先让左子树最右节点抓住旧右,再覆盖 n.Right,最后把左指针置空。
Go:原地压平
func flatten(root *TreeNode) {for root != nil {if root.Left != nil {t := root.Leftfor t.Right != nil { t = t.Right }t.Right = root.Rightroot.Right = root.Leftroot.Left = nil}root = root.Right}}
1沿即将形成的右链逐个处理。没有左孩子就只前进。
2t 从左孩子出发一路向右,停在左子树的前序末尾。
3先把旧右子树接到 t 上。这一行必须在改 root.Right 之前。
4左子树移到右侧,左指针置空。然后 root 走到新的右孩子,处理下一层接缝。
总结
左子树最右先抓住旧右,再把左子树挪到右侧、左置空。主例压成 1→2→3→4→5。
- 前序接缝是左子树一路向右的末尾。主例里 1 的接缝是 4,2 的接缝是 3。
- t.Right = 旧右 必须写在 root.Right = root.Left 前面,否则丢掉右子树。
- 改完沿右链继续。迭代,额外空间 O(1)。