递归
递归1
- 递归展开左右子树:
- 我们首先将左子树和右子树分别展平(通过递归)。
- 处理根节点:
- 将原来的右子树暂存,接着将展平后的左子树移动到右子树的位置,并将左子树设为
null。
- 将原来的右子树暂存,接着将展平后的左子树移动到右子树的位置,并将左子树设为
- 连接右子树:
- 找到当前右子树(即原左子树)的末端,并将暂存的右子树接到末端。
var flatten = function(root) {
if (!root) return;
// 先递归拉平左右子树
flatten(root.left);
flatten(root.right);
// 保存当前的右子树
let rightSubtree = root.right;
// 把左子树拉平后移到右边
root.right = root.left;
root.left = null;
// 找到新的右子树的末端,并接上原来的右子树
let current = root;
while (current.right) {
current = current.right;
}
current.right = rightSubtree;
};
**执行过程: **
1
/ \
2 5
/ \ \
3 4 6
递归调用栈的执行顺序:
flatten(1)调用flatten(2)(左子树)flatten(2)调用flatten(3)(左子树)flatten(3)没有左右子树,直接返回,此时3已经是一个单链表节点。flatten(2)继续调用flatten(4)(右子树)flatten(4)没有左右子树,直接返回,此时4已经是一个单链表节点。- 现在
flatten(2)的左右子树都已经拉平:2.left = 3(已经拉平)2.right = 4(已经拉平)- 执行:
let rightSubtree = 2.right; // rightSubtree = 4 2.right = 2.left; // 2.right = 3 2.left = null; // 左指针置空 let current = 2; while (current.right) current = current.right; // 找到 3 current.right = rightSubtree; // 3.right = 4- 现在
2 → 3 → 4,左子树已经拉平。
flatten(1)继续调用flatten(5)(右子树)flatten(5)调用flatten(6)(右子树)flatten(6)没有左右子树,直接返回。flatten(5)的左右子树已经拉平:5.left = null(没有左子树)5.right = 6(已经拉平)- 执行:
let rightSubtree = 5.right; // rightSubtree = 6 5.right = 5.left; // 5.right = null(因为 5.left 是 null) 5.left = null; let current = 5; while (current.right) current = current.right; // current 仍然是 5 current.right = rightSubtree; // 5.right = 6- 现在
5 → 6,右子树已经拉平。
- 回到
flatten(1),现在:
1.left = 2 → 3 → 4(已经拉平)1.right = 5 → 6(已经拉平)- 执行:
let rightSubtree = 1.right; // rightSubtree = 5 → 6
1.right = 1.left; // 1.right = 2 → 3 → 4
1.left = null;
let current = 1;
while (current.right) current = current.right; // 找到 4
current.right = rightSubtree; // 4.right = 5 → 6
- 最终链表:
1 → 2 → 3 → 4 → 5 → 6。
递归2
var flatten = function (root) {
let prev = null
var dfs = function (node) {
if (!node) return
dfs(node.right)
dfs(node.left)
node.right = prev
node.left = null
prev = node
}
dfs(root)
};
先序遍历是根左右,这里来反向进行构建,右左根的顺序来进行遍历。
从上面的代码可以看到,先进行右子树的递归,再进行左子树。
prev用来记录上一轮的头指针。
最后的指针的路径就是下图所示
Step1: Step2: Step3:
1 1 1
/ \ / \ / \
2 5 2 5 2 5
/ \ \ / \ \ / \ \
3 4 6 3 4 6 3 4 6
(right->6) (right->5)
Step4: Step5: Step6:
1 1 1
/ \ / \ / \
2 5 2 5 2 5
/ \ \ / \ \ / \ \
3 4 6 3 4 6 3 4 6
(right->4) (right->3) (right->2)
最后flatten为:
1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null
非递归
- 使用栈模拟递归:
- 我们通过栈来保存节点,并按照右子树、左子树的顺序来展开树。
- 链接节点:
- 每次弹出栈顶节点,并将其连接到前一个节点的右子树,同时清空左子树。
var flatten = function(root) {
if (!root) return;
let stack = [root];
let prev = null;
while (stack.length) {
let current = stack.pop();
if (prev) {
prev.right = current;
prev.left = null;
}
if (current.right) stack.push(current.right);
if (current.left) stack.push(current.left);
prev = current;
}
};