149 lines
6.0 KiB
JavaScript
149 lines
6.0 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|