剑指Offer系列算法-JavaScript解法-2|刷题打卡

236 阅读4分钟

第二天打卡,选了我的刷题列表中第一道中等难度还没做的题,是一道二叉树的算法题,二叉树作为一种基本的数据结构,在学习和工作中都非常常见,还是值得一刷的,今天这道题是一道根据前序和中序遍历的数组获取树的算法,请看:

一、先看题

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]

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

    3
   / \
  9  20
    /  \
   15   7

限制:

  • 0 <= 节点个数 <= 5000

二、整理思路

树的前中后序遍历都属于算法基础,不懂的请自行反思。 首先分析题目,可以得到:

  1. 已有前序遍历的数组,那么从根节点起手建立树更为方便和易于理解
  2. 建立二叉树子树的过程采用的是和父母同一套逻辑,因此很容易可以想到递归处理
  3. 由于所有结果中都不含重复的数字,所以不用担心同一个顺序有多种情况
  4. 考虑边界情况节点为0的情况

根据以上的分析,我写出了以下代码

/**
 * 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}
 */
var buildTree = function (preorder, inorder) {
    if (preorder.length !== inorder.length || preorder.length == 0 || inorder.length == 0) {
        return null
    }
    let tree = new TreeNode(preorder[0])
    let idx = inorder.indexOf(preorder[0])
    tree.left = buildTree(preorder.slice(1, idx + 1), inorder.slice(0, idx))
    tree.right = buildTree(preorder.slice(idx + 1), inorder.slice(idx + 1))
    return tree
};

这种解法算是最普通的解法,由于多次使用了slice()方法,创建了多个数组,因此在时间和空间耗费上大幅增加,非常糟糕。

如果我们能用其他损耗更小的方式获取子树的前序和中序遍历结果,那么就能提高算法性能,于是后续参考其他人的代码进行了优化处理,该方式将子树遍历结果首位和末尾的索引通过传参的形式记录了下来,免去了生成新数组的必要,大幅提高了性能:

/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 * 为了增加可读性,重新对算法变量进行了命名
 * @rootIdx 当前节点在preorder中的索引
 * @srtIdx 当前树的遍历在inorder中的开始索引
 * @endIdx 当前树的遍历在inorder中的结束索引
 */
var buildTree = function (preorder, inorder, rootIdx,srtIdx,endIdx) {
    if(rootIdx==undefined){
        rootIdx=srtIdx=0
        endIdx=inorder.length-1
    }
    if (srtIdx>endIdx) return null
    let tree = new TreeNode(preorder[rootIdx])
    //由于所有节点的值都不一样,因此可以用indexOf获取索引
    let inRtIdx = inorder.indexOf(preorder[rootIdx])
    tree.left = buildTree(preorder, inorder, rootIdx + 1,srtIdx,inRtIdx-1)
    tree.right = buildTree(preorder, inorder, rootIdx + (inRtIdx-srtIdx)+1,inRtIdx+1,endIdx)
    return tree
};

该算法中,记录了当前树在inorder数组中的范围,对于当前树来说,他的左子树的根节点索引(leftIdx)即为前序遍历中 当前树根节点索引+1(rootIdx+1),而右子树的根节点索引(rightIdx),即为 当前当前树根节点索引+左子树长度+1,因此需要树的索引范围来辅助计算左子树的长度,从而避免了使用slice而导致的性能损耗。

三、总结

最初拿到题目的时候完全没头绪,只能想到从根节点开始,连递归法都没能想出来,察觉到了自己的基础之薄弱。

后来参考之后,第一次写的时候用的for循环,繁琐又不美观,随后参考学习别人的写法才想到用indexOf就可以解决问题,这又是对JS中常用API的掌握不够。

优化的方法虽然想了很久还是没想到怎么做,也是学习之后豁然开朗。(指花了两小时想明白逻辑)

虽然只是一个常见的中等难度的二叉树问题,却是让自己意识到了很多不足和薄弱的地方,再接再厉吧。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情