algorighm/top-interview-leetcode150/binary-tree/105从前序与中序遍历序列构造二叉树.js

149 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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)
* }
*/
/**
* https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
/*
二叉树的问题都能通过递归抽象解决,利用前序遍历的结果和中序遍历的结果也可以通过抽象解决。
抽象问题:以最简单的二叉树[1,2,3]来举例,他的中序遍历结果为[2,1,3],前序遍历的第一个结果为
根节点,而在中序遍历中,根节点恰好把遍历结果分成了左右两部分,左侧为根节点左子树的遍历结果
右侧为根节点右子树的遍历结果,如果我们能找到根节点左子树的前序遍历结果,和右子树的前序遍历结果
就可以递归调用,恢复整棵二叉树,这一点我们能从根节点的二叉树,获取。
过程分析:
以[1,2,4,5,3,6,7]这个二叉树为例根据上面的思路我们可以找到二叉树的根节点值1然后利用中序
遍历结果[4,2,5,1,6,3,7],将二叉树左右子树的中序遍历结果获取到,分别为[4,2,5],[6,3,7],仔细观察
前序遍历的结果[1,2,4,5,3,6,7]也能获取左子树的前序遍历,那就是从根节点后面的左子树个数个数,也就是
根节点后面的三个数,右子树的前序遍历同理,之后递归调用即可
*/
import TreeNode from './tool';
const buildTree = function (preorder, inorder) {
};
function f1(preorder, inorder) {
if (preorder.length === 0) return null; // 如果子树没有节点直接返回null
// 根据preorder的第一个值在inorder中定位根节点并把遍历结果分成左右子树两部分
const rootIndex = inorder.indexOf(preorder[0]);
const leftInOrder = inorder.slice(0, rootIndex);
const rightInOrder = inorder.slice(rootIndex + 1);
// 根据左子树和右子树的节点个数获取前序遍历的结果
const leftPreOrder = preorder.slice(1, 1 + rootIndex);
const rightPreOrder = preorder.slice(1 + rootIndex);
// 通过子树的前中序遍历结果递归构建左右子树
// const left = f1(leftPreOrder, leftInOrder);
// const right = f1(rightPreOrder, rightInOrder);
// 创建根节点
// const root = new TreeNode(preorder[0], left, right);
// return root;
// 一次性返回上面的过程
return new TreeNode(preorder[0], f1(leftPreOrder, leftInOrder), f1(rightPreOrder, rightInOrder));
}
/*
f1的做法思路非常清晰但是分割数组会消耗大量内存实际做法应该使用分割左右子树这样能节省
大量的空间,而且免去了分割数组的复杂度
思考只需传递三个下标第一个指向前序的root,第二个和第三个指向中序的结果左右边界
hard: 右子树的左右边界比较难计算,多思考几下
*/
function f2(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
if (inOrderLeft > inOrderRight) return null;
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
const rootIndex = inorder.indexOf(preorder[preOrderIndex]);
const left = f2(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
// eslint-disable-next-line max-len
const right = f2(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
return new TreeNode(preorder[preOrderIndex], left, right);
}
/*
上面代码已经十分简洁了但是还有一个可以优化的地方就是indexOf这个找过程题目中所给的测试
用例值是没有重复的所以我们可以建立一个hashMap前序遍历的值作为下标前序遍历的下标作为值
到时候查早下标只需O(1)的时间复杂度
*/
function f3(preO, inO) {
// 构建map映射
const map = new Map();
for (let i = 0; i < inO.length; i++) {
map.set(inO[i], i);
}
return build(preO, inO, 0, 0, inO.length - 1);
/**
*
* @param {*} preorder 前序遍历结果
* @param {*} inorder 中序遍历结果
* @param {*} preOrderIndex 前序遍历的根节点值
* @param {*} inOrderLeft 中序遍历的左边界
* @param {*} inOrderRight 中序遍历的右边界
* @returns
*/
function build(preorder, inorder, preOrderIndex, inOrderLeft, inOrderRight) {
if (inOrderLeft > inOrderRight) return null;
// 根据前序遍历的第一个结果,也就是根节点的值,将中序遍历分割成左右子树
const rootIndex = map.get(preOrderIndex[preOrderIndex]);
const left = build(preorder, inorder, preOrderIndex + 1, inOrderLeft, rootIndex - 1);
// eslint-disable-next-line max-len
const right = build(preorder, inorder, preOrderIndex + (rootIndex - inOrderLeft) + 1, rootIndex + 1, inOrderRight);
return new TreeNode(preorder[preOrderIndex], left, right);
}
}
// REVIEW:
/*
利用迭代:
*/
function f4(preorder, inorder) {
if (!preorder || preorder.length === 0) return null;
// 根节点是前序遍历的第一个元素
const root = new TreeNode(preorder[0]);
// 栈用于存储节点
const stack = [root];
let inorderIndex = 0;
// 从前序遍历数组的第二个元素开始遍历
for (let i = 1; i < preorder.length; i++) {
const preorderVal = preorder[i];
let node = stack[stack.length - 1];
// 如果栈顶元素的值不等于当前中序遍历的值,说明是左子树的节点
if (node.val !== inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
// 否则说明当前节点是右子树的节点
while (stack.length > 0 && stack[stack.length - 1].val === inorder[inorderIndex]) {
node = stack.pop(); // 回溯
inorderIndex++;
}
// 创建右子树节点并将其压入栈
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}