/** * 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; }