[前端]_一起刷leetcode 105. 从前序与中序遍历序列构造二叉树

265 阅读4分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

105. 从前序与中序遍历序列构造二叉树

给定一棵树的前序遍历 preorder 与中序遍历  inorder。请构造二叉树并返回其根节点。

 

示例 1:

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

 

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均无重复元素
  • inorder 均出现在 preorder
  • preorder 保证为二叉树的前序遍历序列
  • inorder 保证为二叉树的中序遍历序列

思路

  1. 做这道题目之前一定要先搞懂什么是前序遍历,什么是中序遍历,如果还不知道的小伙伴们可以从我这篇文章看起,[路飞]_一文彻底搞懂二叉树的前序、中序、后序遍历;总结一下就是:

    (1)前序遍历: 先访问根结点,然后遍历左子树,最后遍历右子树。

    (2)中序遍历: 先遍历左子树,然后访问根结点,最后遍历右子树。

  2. 那么我们的根节点已经找到了,就是前序遍历preorder中的第一个元素,然后再去中序遍历inorder中找到根节点所在的位置index

  3. 这样子从前序遍历preorder索引1开始,到index中的元素就是左子树,index后的元素就是右子树。而且索引1就是根节点的左子节点,索引index + 1就是根节点的右子节点, 这样子就可以递归求解了;

  4. 但是每一轮我们找索引可能会很麻烦,由于题目中已经保证了不存在重复元素,那么我们可以建立起一个map,去维护值和索引之间的映射关系。

暴力实现

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
function buildTree(preorder, inorder) {
    // 前序遍历的第一个节点是根节点
    const node = new TreeNode(preorder[0]);
    // 找到根节点在中序遍历中的位置
    let index = inorder.findIndex(v => v === preorder[0]);
    
    // 如果中序遍历左边有元素,说明有左子节点,递归寻找
    if (index > 0) {
        node.left = buildTree(preorder.slice(1, index + 1), inorder.slice(0, index));
    }

    // 如果中序遍历右边有元素,说明有右子节点,递归寻找
    if (index < preorder.length - 1) {
        node.right = buildTree(preorder.slice(index + 1), inorder.slice(index + 1));
    }

    return node;
}

优化

每次我们都需要去遍历找根节点在中序遍历中的index, 有点不方便,我们可以一开始用map保存起来中序遍历的节点的关系,但是用了这个方式每次就不能切割数组了,只能缓存原数组的开始坐标位置和结束坐标位置,用官方的图就是这样子的一个公式。

image.png

优化代码

var buildTree = function(preorder, inorder) {
    let map = new Map(inorder.map((item, index) => [item, index]));
    return getChildNode(preorder, map, 0, preorder.length - 1, 0, inorder.length - 1);
};


function getChildNode(preorder, map, pLeft, pRight, iLeft) {
    // 前序遍历边界溢出即结束
    if (pLeft > pRight) {
        return null;
    }

    // 前序遍历第一个节点就是根节点
    const node = new TreeNode(preorder[pLeft]);
    // 找到根节点在中序遍历中的位置
    let index = map.get(preorder[pLeft]);

    // 把原本的切割数组递归改成用指针替代
    node.left = getChildNode(preorder, map, pLeft + 1, index - iLeft + pLeft, iLeft);
    node.right = getChildNode(preorder, map, index - iLeft + pLeft + 1, pRight, index + 1);

    return node;
}

结果

image.png

栈解法

思路

  1. 用栈stack保存当前所有可能包含右子节点的节点,每次新增左子节点时候入栈,然后去匹配在中序遍历中,如果下一个值不是栈中的上一个节点,说明有右节点;
  2. 每次遇到节点,判断它是否中序遍历的第一个节点,如果不是,说明还有左子节点,入栈;
  3. 如果是的话,取出当前节点,然后判断当前节点有没有右子节点,有的话建立起当前节点和右子节点的关系,然后把右子节点入栈做判断。
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
    // 存放前序遍历中未用到的项
    let stack = [];
    // 存放中序遍历的索引
    let index = 0;

    // 加个伪节点,不用每次都判断是否起点, 起个不可能出现的数字
    let root = new TreeNode(NaN);
    stack.push(root);

    // 前序遍历跑一遍
    while (preorder.length) {
        // 判断是右节点还是左节点
        if (stack[stack.length - 1].val === inorder[index]) {
            let prev = stack.pop();
            index++;
            // 如果中序遍历到了根节点,那么我们需要判断有没有右节点
            // 即stack中的下个节点如果不是中序遍历的下个节点,说明中间有右节点
            if (stack[stack.length - 1].val !== inorder[index]) {
                const nextNode = new TreeNode(preorder.shift());
                prev.right = nextNode;
                stack.push(nextNode);
            }
        } else {
            // 如果是左节点,新建节点,构造关系,入栈即可
            // 新建节点
            const nextNode = new TreeNode(preorder.shift());
            // 构造关系
            stack[stack.length - 1].left = nextNode;
            // 入栈
            stack.push(nextNode); 
        }
    }

    return root.left;
};

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。