Leetcode105-前序中序来构造二叉树-Python-分解问题的思路

这是一个非常经典的二叉树构建问题(LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal)。

你的代码采用了DFS(深度优先搜索)+ 哈希表优化 的策略,逻辑非常清晰。不过,我注意到代码中构建哈希表的地方有一个小小的逻辑反转 需要修正(k:v 应该是 v:k,因为我们需要通过"值"查"下标")。

以下是为你生成的博客文章,我已经修正了那个小瑕疵,并添加了图解说明:


算法图解:如何从前序与中序遍历"复原"二叉树

在二叉树的算法题中,**"构造二叉树"**系列绝对是重难点。今天我们来拆解 LeetCode 105 题:给定两个数组 preorder(前序遍历)和 inorder(中序遍历),如何通过递归的方式完美复原这棵树?

核心思路:分而治之

解决这个问题的关键在于理解两种遍历方式的排布规律

  1. 前序遍历 (Preorder)[ 根节点 | 左子树所有节点 | 右子树所有节点 ]

    • 特性 :数组的第一个元素 永远是当前的根节点
  2. 中序遍历 (Inorder)[ 左子树所有节点 | 根节点 | 右子树所有节点 ]

    • 特性:一旦我们找到了根节点的位置,它左边的就是左子树,右边的就是右子树。

破局步骤

我们的策略可以总结为 "定位根 -> 切分左右 -> 递归构建"

  1. preorder 的第一个元素拿到 Root

  2. inorder 中找到这个 Root 的下标 (记为 rootIdx)。

  3. 通过 rootIdx,计算出左子树的长度 (leftLen)。

  4. 利用 leftLen,我们将 preorderinorder 两个数组精确地"切"成两半,分别丢给递归函数去构建左孩子和右孩子。


逻辑图解

假设:

  • preorder = [3, 9, 20, 15, 7]

  • inorder = [9, 3, 15, 20, 7]

第一轮递归:

  1. 找根preorder[0]3

  2. 定位 :在 inorder 中找到 3 的位置,下标是 1

  3. 切分

    • 左子树 :长度为 1(即 9)。

    • 右子树 :剩下的部分(即 15, 20, 7)。

此时我们可以确定根节点 3 的左右边界,然后递归下去。


代码实现 (Python)

为了避免每次都在 inorder 中遍历寻找根节点(那样会导致时间复杂度变成 O(N\^2)),我们使用一个 哈希表 (Hash Map) 预先存储 值 -> 下标 的映射,实现 O(1) 的快速查找。

Python

复制代码
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        # ⚡️ 优化点:构建哈希表,通过"数值"快速查到"中序遍历中的下标"
        # 注意:这里是 val : index
        idxInorder = {val: i for i, val in enumerate(inorder)}
        
        # 定义 DFS 函数
        # pStart, pEnd: 前序遍历的范围
        # iStart, iEnd: 中序遍历的范围
        def dfs(pStart, pEnd, iStart, iEnd):
            # 🛑 终止条件:范围无效(比如 pStart > pEnd),说明子树为空
            if pStart > pEnd or iStart > iEnd:
                return None
            
            # 1. 建立根节点
            root_val = preorder[pStart]
            root = TreeNode(root_val)
            
            # 2. 在中序遍历中找到根节点的位置
            rootIdx = idxInorder[root_val]
            
            # 3. 计算左子树的节点数量 (关键步骤!)
            leftLen = rootIdx - iStart
            
            # 4. 递归构建左右子树
            # 左子树:
            # Preorder范围: [pStart+1, pStart+leftLen]
            # Inorder范围:  [iStart, rootIdx-1]
            root.left = dfs(pStart + 1, pStart + leftLen, iStart, rootIdx - 1)
            
            # 右子树:
            # Preorder范围: [pStart+leftLen+1, pEnd]
            # Inorder范围:  [rootIdx+1, iEnd]
            root.right = dfs(pStart + leftLen + 1, pEnd, rootIdx + 1, iEnd)
            
            return root
            
        return dfs(0, len(preorder)-1, 0, len(inorder)-1)

难点解析:索引计算

代码中最容易晕的地方就是递归时的索引范围,一定要死记这个公式:

  • 左子树长度 (leftLen) = rootIdx (根在中序的位置) - iStart (中序的起始位置)。

  • 左孩子的前序范围 :从 pStart + 1 开始,长度为 leftLen,所以结束于 pStart + leftLen

  • 右孩子的前序范围 :紧接着左孩子之后,即 pStart + leftLen + 1 开始,直到 pEnd


复杂度分析

  • 时间复杂度O(N)

    • 我们需要遍历所有节点一次来构建树。

    • 哈希表的查找是 O(1) 的,构建哈希表也是 O(N)

  • 空间复杂度O(N)

    • 哈希表占用了 O(N) 的空间。

    • 递归调用栈在最坏情况(链状树)下也需要 O(N) 的空间。

总结

这道题是理解 分治算法 (Divide and Conquer) 在树结构中应用的绝佳案例。

只要记住 "前序定根,中序定界" 这八字口诀,配合哈希表提速,就能轻松拿下!

希望这篇解析能帮你彻底搞懂二叉树的构建!

相关推荐
wszy18092 分钟前
顶部标题栏的设计与实现:让用户知道自己在哪
java·python·react native·harmonyos
kaizq14 分钟前
AI-MCP-SQLite-SSE本地服务及CherryStudio便捷应用
python·sqlite·llm·sse·mcp·cherry studio·fastmcp
charlie1145141911 小时前
嵌入式现代C++教程: 构造函数优化:初始化列表 vs 成员赋值
开发语言·c++·笔记·学习·嵌入式·现代c++
wjs20241 小时前
Bootstrap5 消息弹窗
开发语言
资生算法程序员_畅想家_剑魔1 小时前
Kotlin常见技术分享-02-相对于Java 的核心优势-协程
java·开发语言·kotlin
IT=>小脑虎1 小时前
C++零基础衔接进阶知识点【详解版】
开发语言·c++·学习
nbsaas-boot2 小时前
Go vs Java 的三阶段切换路线图
java·开发语言·golang
码农小韩2 小时前
基于Linux的C++学习——指针
linux·开发语言·c++·学习·算法
!chen2 小时前
Error: error:0308010C:digital envelope routines::unsupporte
python
微露清风2 小时前
系统性学习C++-第十九讲-unordered_map 和 unordered_set 的使用
开发语言·c++·学习