给你二叉树的根结点
root
,请你将它展开为一个单链表:展开后的单链表应该同样使用
TreeNode
,其中right
子指针指向链表中下一个结点,而左子指针始终为null
。展开后的单链表应该与二叉树 先序遍历 顺序相同。
解法1 暴力解法
思路
和之前链表的思路一致,先将节点缓存到数组当中。之后再去改变其结构。
不过要注意的是遍历的顺序是先序遍历。
代码
js
function flatten(root: TreeNode | null): void {
if (!root) return;
const list = [];
const preorder = (node) => {
if (!node) return;
list.push(node);
preorder(node.left);
preorder(node.right);
};
preorder(root);
for (let i = 0; i < list.length; i++) {
list[i].left = null;
list[i].right = list[i + 1];
}
// 最后一个节点也需要置空其左右子树
const last = list[list.length - 1];
last.left = null;
last.right = null;
};
时空复杂度
时间复杂度:O(n)
空间复杂度:O(n)
解法2 莫里斯原地修改
思路
Morris 遍历的核心思想是左子树右移 + 接尾巴
原理:
对于每个当前节点 curr
:
- 如果
curr
有左子树:
- 找到它左子树中最右边的节点
predecessor
。 - 将
curr.right
接到这个predecessor.right
。 - 把
curr.left
移动到curr.right
,然后置空curr.left
。
- 把
curr
往右移。
代码
js
function flatten(root: TreeNode | null): void {
if (!root) return;
let curr = root;
while (curr) {
if (curr.left) {
// 找到左子树的最右节点
let pre = curr.left;
while (pre.right) {
pre = pre.right;
}
// 将 curr 的右子树接到 pre.right
pre.right = curr.right;
// 把左子树移到右边
curr.right = curr.left;
curr.left = null;
}
// 移动到下一个节点
curr = curr.right;
}
};
时空复杂度
时间复杂度:O(n)
空间复杂度:原地修改 O(1)