LeetCode 105. 从前序与中序遍历序列构造二叉树:分治递归思路详解

LeetCode 105. 从前序与中序遍历序列构造二叉树:分治递归思路详解

前言

LeetCode 105 是二叉树构造题中的经典代表,也是面试高频题。

很多人知道:

前序可以找到根节点,中序可以划分左右子树。

但是写代码时,经常会卡在:

  • 左子树区间怎么算?
  • 右子树区间怎么算?
  • 为什么需要哈希表?

这篇文章就把整个递归过程一步一步讲清楚。


一、题目描述

给定:

  • 前序遍历数组 preorder
  • 中序遍历数组 inorder

要求恢复原来的二叉树。

例如:

text 复制代码
preorder

3 9 20 15 7

inorder

9 3 15 20 7

恢复后的树:

text 复制代码
      3
     / \
    9   20
       /  \
      15   7

二、先理解两种遍历的特点

前序遍历

顺序:

复制代码
根
左
右

因此:

数组第一个元素一定是当前子树的根节点。

例如:

复制代码
3 9 20 15 7

根就是:

复制代码
3

中序遍历

顺序:

复制代码
左
根
右

例如:

复制代码
9 3 15 20 7

找到根:

复制代码
3

左边:

复制代码
9

全部属于左子树。

右边:

复制代码
15 20 7

全部属于右子树。

所以:

中序遍历负责:

划分左右子树。


三、为什么能够递归?

恢复整棵树,其实就是不断重复下面三件事:

第一步:

前序第一个元素找根。

第二步:

中序找到根的位置。

第三步:

递归恢复左右子树。

整个过程完全一致。

所以天然适合递归。


四、如何切分区间?

这是整道题最关键的地方。

例如:

复制代码
preorder

3 9 20 15 7

inorder

9 3 15 20 7

根:

复制代码
3

在中序的位置:

复制代码
9 | 3 | 15 20 7

左子树节点数量:

复制代码
1

因此:

前序中:

复制代码
根 左子树 右子树

3 | 9 | 20 15 7

左子树也只有一个节点。

所以:

左子树区间就确定了。

右子树自然也就确定了。

整个递归都是利用:

左子树节点数量。

来不断切分前序数组。


五、为什么使用哈希表?

如果每次都去中序数组查找根节点:

python 复制代码
inorder.index(root)

时间复杂度:

复制代码
O(n)

递归过程中会查很多次。

整体退化成:

复制代码
O(n²)

因此提前建立:

python 复制代码
value -> index

哈希表。

例如:

python 复制代码
{
9:0,
3:1,
15:2,
20:3,
7:4
}

以后查找:

复制代码
O(1)

整体复杂度降低到:

复制代码
O(n)

六、递归流程

整个递归可以概括为四步。

第一步

前序第一个元素作为当前根节点。


第二步

通过哈希表找到根节点在中序中的位置。


第三步

根据左子树节点数量切分前序区间。


第四步

递归构造左右子树。

直到区间为空。

返回 None。


七、完整代码

python 复制代码
class Solution:
    def buildTree(self, preorder, inorder):

        index = {value: i for i, value in enumerate(inorder)}

        def build(pre_left, pre_right, in_left, in_right):

            if pre_left > pre_right:
                return None

            root_val = preorder[pre_left]

            root = TreeNode(root_val)

            mid = index[root_val]

            left_size = mid - in_left

            root.left = build(
                pre_left + 1,
                pre_left + left_size,
                in_left,
                mid - 1
            )

            root.right = build(
                pre_left + left_size + 1,
                pre_right,
                mid + 1,
                in_right
            )

            return root

        return build(
            0,
            len(preorder) - 1,
            0,
            len(inorder) - 1
        )

八、复杂度分析

时间复杂度

复制代码
O(n)

每个节点只访问一次。


空间复杂度

复制代码
O(n)

主要来自:

  • 哈希表
  • 递归栈

九、高频易错点

1、没有建立哈希表

直接使用:

python 复制代码
index()

会导致:

复制代码
O(n²)

2、左右区间切分错误

真正决定切分的是:

复制代码
左子树节点数量

而不是:

根节点的位置。


3、遗漏终止条件

必须写:

python 复制代码
if pre_left > pre_right:
    return None

否则容易数组越界。


4、不断切片数组

例如:

python 复制代码
preorder[:k]

虽然能写出来。

但是每次都会复制数组。

空间和时间都会增加。

面试中更推荐:

使用左右边界表示区间。


十、总结

这道题其实就是不断重复三个动作:

  1. 前序找到根节点。
  2. 中序划分左右子树。
  3. 根据左子树节点数量切分前序区间,递归继续构造。

掌握了这种「前序确定根、中序划分左右、递归分治」的思路,不仅能解决 LeetCode 105,还能应对很多二叉树重建类题目,是二叉树递归中的经典模板。

相关推荐
可编程芯片开发1 小时前
基于PI控制的三相整流器控制系统的simulink建模与仿真,包含超级电容充电和电机
算法
酿情师2 小时前
区块链共识算法深度拆解:PoW、PoS、PBFT、Raft 原理解析
算法·区块链·共识算法
hansang_IR2 小时前
【记录】「SCOI2016」三道模拟赛/26.7.12
c++·算法
QQ骞2 小时前
【ECU-HIL 嵌入式车载软件测试学习路线和面试描述方式(1)】
学习·面试·职场和发展
退休倒计时2 小时前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
不是az2 小时前
力控相关知识点
算法·机器学习·最小二乘法
战血石LoveYY3 小时前
Integer类型超限,导致数据异常
java·算法
紫金修道3 小时前
PnP算法介绍(Perspective-n-Point)
算法
mm9954204 小时前
PMI三大证书:PMP、PgMP、PfMP证书对比
学习·职场和发展·项目管理·学习方法·pmp