LeetCode - 面试题07 - 重建二叉树

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 题目
三 解题思路
四 统计分析
五 解题套路

二 题目

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
const buildTree = (preorder, inorder) => {

}

根据上面的已知函数,小伙伴们可以先尝试破解本题,确定了自己的答案后再看下面代码。

三 解题思路

拿到题目,咱们先了解前序遍历和中序遍历:

  • 前序遍历:根节点、左子树、右子树。每个子树的遍历顺序同样满足前序遍历顺序。

  • 中序遍历:左子树、根节点、右子树。每个子树的遍历顺序同样满足中序遍历顺序。

OK,这时候需要先声明一下:

  • 没做过树的简单题目的小伙伴可以先行撤退了,这道题是中等难度题目

咱们还是使用两种方法:

  • 递归和迭代


解法一:递归

/**
* @name buildTree
* @description  重建二叉树
* @param {array[number]} preorder 先序遍历数组
* @param {array[number]} inorder 中序遍历数组
*/
const buildTree = (preorder, inorder) => {
  // 如果当前先序遍历数组没有长度,说明里面没有节点了,返回 null
  if (!preorder.length) {
    return null;
  }
  // 如果先序遍历的数组有内容,那么当前跟节点肯定是它的第 1 个元素
  const rootValue = preorder[0];
  const root = {
    val: preorder[0],
    left: null,
    right: null,
  };

  // 获取到当前元素(第 1 个元素)的位置
  const rootInIndex = inorder.indexOf(rootValue);

  // 裁剪左边列表
  const leftPre = preorder.slice(1, rootInIndex + 1);
  const leftIn = inorder.slice(0, rootInIndex);

  // 裁剪右边列表
  const rightPre = preorder.slice(rootInIndex + 1);
  const rightIn = inorder.slice(rootInIndex + 1);

  // 进一步递归判断
  root.left = buildTree(leftPre, leftIn);
  root.right = buildTree(rightPre, rightIn);

  // 返回节点
  return root;
};

const preorder = [3, 9, 20, 15, 7];
const inorder = [9, 3, 15, 20, 7];
console.log(buildTree(preorder, inorder));

递归的方式可能比较容易讲解。

先给代码注入灵魂:

console.log('------');
console.log(`此时的值是:${rootValue}`);
console.log(`它中序遍历中的索引:${rootInIndex}`);
console.log(`它在先序的左部位:${leftPre}`);
console.log(`它在中序的左部位:${leftIn}`);
console.log(`它在先序的右部位:${rightPre}`);
console.log(`它在中序的右部位:${rightIn}`);

然后查看它们的输出:

------
此时的值是:3
它中序遍历中的索引:1
它在先序的左部位:9
它在中序的左部位:9
它在先序的右部位:20,15,7
它在中序的右部位:15,20,7
------
此时的值是:9
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:
------
此时的值是:20
它中序遍历中的索引:1
它在先序的左部位:15
它在中序的左部位:15
它在先序的右部位:7
它在中序的右部位:7
------
此时的值是:15
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:
------
此时的值是:7
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:

可以看到,我们通过将先序和中序数组逐步拆解的方式,将它们逐步给分解下去。

当它们的左右部位为空的时候,触发 if (!preorder.length) { } 返回 null

最终拼凑成一块。

这里 jsliang 的描述可能不够完善,小伙伴如果不太熟悉可以打印下代码进一步了解。

如果你很感兴趣,非常想懂为何如此,欢迎加 jsliang 的微信进一步探讨。GitHub:https://github.com/LiangJunrong/document-library


迭代

const buildTree = (preorder, inorder) => {
  if (!preorder.length) {
    return null;
  }
  const root = {
    val: preorder[0],
    left: null,
    right: null,
  };
  const stack = [];
  stack.push(root);
  let inorderIndex = 0;
  for (let i = 1; i < preorder.length; i++) {
    let node = stack[stack.length - 1];
    if (node.val !== inorder[inorderIndex]) {
      node.left = {
        val: preorder[i],
        left: null,
        right: null,
      };
      stack.push(node.left);
    } else {
      while (stack.length && stack[stack.length - 1].val === inorder[inorderIndex]) {
        node = stack.pop();
        inorderIndex++;
      }
      node.right = {
        val: preorder[i],
        left: null,
        right: null,
      };
      stack.push(node.right);
    }
  }
  return root;
};

const preorder = [3, 9, 20, 15, 7];
const inorder = [9, 3, 15, 20, 7];
console.log(buildTree(preorder, inorder));

相对而言,迭代的代码是我直接抄官方解答的,如果小伙伴了解了递归,那么迭代的方法也差不多可以了解,这里就不哆嗦了。

相对而言,发现中等难度的题目的确不容易讲解,个人感觉这次砸锅了,讲得不够通透,后续我写小册的时候应该会配合 GIF 图等演示。

四 统计分析

解法执行用时 / 击败率内存消耗 / 击败率
解法 1148 ms / 35.06%%127.2 MB / 100.00%
解法 188 ms / 91.00%36.3 MB / 100.00%

五 套路分析

本题套路为:在对付树和链表的遍历上,我们优先采取递归和迭代的解题方式,争取尽可能快地破解,然后根据他人的内容进一步补充完善自己

该套路后面会更新到 LeetCode 目录下的刷题技巧上,小伙伴可以通过 jsliang 的仓库找到。

路径:document-library/other-library/LeetCode/刷题技巧

如果小伙伴有更好的思路想法,或者没看懂其中某种解法,欢迎评论留言或者私聊 jsliang~


不折腾的前端,和咸鱼有什么区别!

jsliang 会每天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。

浪子神剑 会每天更新面试题,以面试题为驱动来带动大家学习,坚持每天学习与思考,每天进步一点!

扫描上方二维码,关注 jsliang 的公众号(左)和 浪子神剑 的公众号(右),让我们一起折腾!

jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于https://github.com/LiangJunrong/document-library上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值