每天学习一点算法 2026/03/11
题目:从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
我们知道先序遍历的遍历顺序是根节点→左子节点→右子节点,中序遍历的遍历顺序是左子节点→根节点→右子节点。
我们以 preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]为例
-
首先可以得到
preorder的第一项3是总的根节点 -
然后我们在
inorder中找到3, 那么3左边的部分[9]就是左子树,3右边的部分[15,20,7]就是右子树 -
因为左子树有内容,我们将
preorder的第二项9就是3节点的左子节点3 / 9 -
然后
9作为根节点,[9]中找到9,左右都没有元素了,所以他就是叶子结点了 -
然后
preorder第三项20就是3的右节点3 / \ 9 20 -
然后
20作为根节点,[15,20,7]中找到20,分为左边部分[15]和 右边部分[7],后面也就按这个步骤一直分割就行,这样就可以得到树结构3 / \ 9 20 / \ 15 7
我们可以看到,我们需要做的是,遍历先序的数组,然后不断分割中序数组,最容易想到的就是递归
typescript
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
let index = 0 // preorder 指针
return helper(inorder) // 调用辅助函数递归生成树
function helper(arr: number[]) {
if (arr.length === 0) return null // 分割结果为空是返回null
const node = new TreeNode(preorder[index]) // 根据当前指针创建当前根节点
const i = arr.findIndex(val => preorder[index] === val) // 找到根节点在 arr 中位置
if (i > 0) {
// 存在左树数据
index++
node.left = helper(arr.slice(0, i))
}
if (i < arr.length - 1) {
// 存在右树数据
index++
node.right = helper(arr.slice(i + 1))
}
return node // 返回当前根节点
}
};
还有一个迭代的方法:
有点复杂我们直接用一个例子来讲:
3
/ \
9 20
/ / \
8 15 7
/ \
5 10
/
4
preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7]
inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]
我们观察一下 preorder[i] 和 preorder[i + 1],有两种情况
preorder[i + 1]是preorder[i]的左子结点preorder[i]没有左子节点,preorder[i + 1]是preorder[i]某一个父节点的 右子节点
根据这个规律我们可以维护一个栈存放判断右节点的节点,通过遍历 preorder 来还原树结构,具体步骤如下:
-
首先定义一个指针指向
inorder的第一个元素4 -
接着我们开始遍历
preorder,首先是3,首位直接入栈 -
然后遍历到
9,关键点来了,我们如何判断这个9是不是3的左节点呢?假设不是的话,3是不是就应该是最左侧的节点,但是我们目前还没做任何操作,最左侧的节点应该是4,所以我们可以确定9是3的左子结点,我们将3左侧指向节点9,并将节点9入栈。 -
然后
8, 5, 4一样的操作,等到10时,我们可以发现 上一个节点4就是当前最左侧的节点,那么10就是节点4的某个父节点 -
我们怎么寻找这个父节点呢?我们可以看到,只有左节点的树前序遍历的结果就是中序遍历结果的反转,那么移动的指针,并弹栈,弹栈节点和指针指向值相等时,就是我们要找的父节点
8,我们将8右侧指向节点9,并将节点9入栈。 -
就这样遍历完所有的
preorder元素就可以还原整个树了function buildTree(preorder: number[], inorder: number[]): TreeNode | null {
if (preorder.length === 0) return null
const stack: TreeNode[] = []
const root = new TreeNode(preorder[0])
stack.push(root)
let index = 0
for (let i = 1; i < preorder.length; i++) {
let node = stack[stack.length - 1]
if (inorder[index] !== node.val) {
node.left = new TreeNode(preorder[i])
stack.push(node.left)
} else {
while (stack.length > 0 && stack[stack.length - 1].val === inorder[index]) {
index++
node = stack.pop()
}
node.right = new TreeNode(preorder[i])
stack.push(node.right)
}} return root};
题目来源:力扣(LeetCode)