【leetcode】114. 二叉树展开为链表

59 阅读3分钟

image.png

递归

递归1

  1. 递归展开左右子树:
    • 我们首先将左子树和右子树分别展平(通过递归)。
  2. 处理根节点:
    • 将原来的右子树暂存,接着将展平后的左子树移动到右子树的位置,并将左子树设为 null
  3. 连接右子树:
    • 找到当前右子树(即原左子树)的末端,并将暂存的右子树接到末端。
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

递归调用栈的执行顺序

  1. flatten(1) 调用 flatten(2)(左子树)
  2. flatten(2) 调用 flatten(3)(左子树)
  3. flatten(3) 没有左右子树,直接返回,此时 3 已经是一个单链表节点。
  4. flatten(2) 继续调用 flatten(4)(右子树)
  5. flatten(4) 没有左右子树,直接返回,此时 4 已经是一个单链表节点。
  6. 现在 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,左子树已经拉平。
  7. flatten(1) 继续调用 flatten(5)(右子树)
  8. flatten(5) 调用 flatten(6)(右子树)
  9. flatten(6) 没有左右子树,直接返回。
  10. 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,右子树已经拉平。
  11. 回到 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;
    }
};