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,还能应对很多二叉树重建类题目,是二叉树递归中的经典模板。

相关推荐
Navigator_Z31 分钟前
LeetCode //C - 1209. Remove All Adjacent Duplicates in String II
c语言·算法·leetcode
ShineWinsu3 小时前
对于 C++:C++14中从变量模板、泛型 Lambda 到并发与字面量的解析
c++·算法
土司大王3 小时前
LeetCode hot100——合并两个有序链表
算法·leetcode·链表
码行山野赴时序归途3 小时前
算法效率的度量(上):时间复杂度中的对数思维
c语言·数据结构·算法
算AI4 小时前
基于LLM的无人机仿真测试:新方法竞赛夺佳绩
人工智能·深度学习·算法·机器学习·ai
kaixin_啊啊5 小时前
专用优化算法LKH
算法
_Narcissus_6 小时前
二分算法笔记及例题
数据结构·c++·笔记·算法·蓝桥杯·查找·二分算法
tachibana26 小时前
RAGAS 指标解读
数据库·人工智能·算法·机器学习·架构·大模型·llm
qq_419563097 小时前
ToT 的 BFS/DFS 有个致命缺口:蒙特卡洛树搜索(MCTS)用「随机试错+统计」让大模型想得更深,小模型 + 它竟超过 GPT-4
算法·深度优先·宽度优先