news 2026/2/16 18:21:31

算法题 根据前序和后序遍历构造二叉树

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法题 根据前序和后序遍历构造二叉树

根据前序和后序遍历构造二叉树

问题描述

给定前序遍历后序遍历的结果,构造并返回二叉树。

注意:理论上,仅凭前序和后序遍历无法唯一确定一棵二叉树(缺少中序遍历的根位置信息)。保证:

  • 所有节点值唯一
  • 返回任意一个满足条件的二叉树即可

遍历定义

  • 前序遍历:根 → 左子树 → 右子树
  • 后序遍历:左子树 → 右子树 → 根

示例

输入:preorder=[1,2,4,5,3,6,7],postorder=[4,5,2,6,7,3,1]输出:[1,2,3,4,5,6,7]解释:1/\23/\/\4567输入:preorder=[1,2,3],postorder=[3,2,1]输出:[1,2,null,null,3][1,null,2,3,null]

算法思路

  1. 核心
    • 前序遍历的第一个元素 = 根节点
    • 后序遍历的最后一个元素 = 根节点
    • 前序遍历的第二个元素 = 左子树的根(如果有左子树)
    • 在后序遍历中找到左子树根的位置,可以确定左子树的范围

代码实现

方法一:递归 + 哈希表

importjava.util.*;// 二叉树节点定义classTreeNode{intval;TreeNodeleft;TreeNoderight;TreeNode(){}TreeNode(intval){this.val=val;}TreeNode(intval,TreeNodeleft,TreeNoderight){this.val=val;this.left=left;this.right=right;}}classSolution{/** * 根据前序和后序遍历构造二叉树 * * @param preorder 前序遍历数组 * @param postorder 后序遍历数组 * @return 构造的二叉树根节点 * * 算法思路: * 1. 使用哈希表存储后序遍历中每个值的索引 * 2. 递归分治:根据左子树根确定左右子树范围 * 3. 默认将不确定的子树作为左子树 */publicTreeNodeconstructFromPrePost(int[]preorder,int[]postorder){// 创建哈希表:值 -> 后序遍历中的索引Map<Integer,Integer>postIndexMap=newHashMap<>();for(inti=0;i<postorder.length;i++){postIndexMap.put(postorder[i],i);}returnbuildTree(preorder,0,preorder.length-1,postorder,0,postorder.length-1,postIndexMap);}/** * 递归构建二叉树 * * @param preorder 前序遍历数组 * @param preStart 前序起始索引 * @param preEnd 前序结束索引 * @param postorder 后序遍历数组 * @param postStart 后序起始索引 * @param postEnd 后序结束索引 * @param postIndexMap 后序值到索引的映射 * @return 子树根节点 */privateTreeNodebuildTree(int[]preorder,intpreStart,intpreEnd,int[]postorder,intpostStart,intpostEnd,Map<Integer,Integer>postIndexMap){// 基础情况:空范围if(preStart>preEnd){returnnull;}// 创建根节点TreeNoderoot=newTreeNode(preorder[preStart]);// 基础情况:只有一个节点if(preStart==preEnd){returnroot;}// 找到左子树的根节点(前序的第二个元素)intleftRootVal=preorder[preStart+1];intleftRootPostIndex=postIndexMap.get(leftRootVal);// 计算左子树的节点数量intleftSubtreeSize=leftRootPostIndex-postStart+1;// 递归构建左子树// 左子树前序范围:[preStart+1, preStart+leftSubtreeSize]// 左子树后序范围:[postStart, leftRootPostIndex]root.left=buildTree(preorder,preStart+1,preStart+leftSubtreeSize,postorder,postStart,leftRootPostIndex,postIndexMap);// 递归构建右子树// 右子树前序范围:[preStart+leftSubtreeSize+1, preEnd]// 右子树后序范围:[leftRootPostIndex+1, postEnd-1]root.right=buildTree(preorder,preStart+leftSubtreeSize+1,preEnd,postorder,leftRootPostIndex+1,postEnd-1,postIndexMap);returnroot;}}

方法二:简洁递归

classSolution{/** * 简洁:不使用额外参数 */publicTreeNodeconstructFromPrePost(int[]preorder,int[]postorder){if(preorder.length==0)returnnull;TreeNoderoot=newTreeNode(preorder[0]);if(preorder.length==1)returnroot;// 找到左子树根在后序中的位置intleftRootIndex=0;for(inti=0;i<postorder.length;i++){if(postorder[i]==preorder[1]){leftRootIndex=i;break;}}// 分割数组intleftSize=leftRootIndex+1;int[]preLeft=Arrays.copyOfRange(preorder,1,1+leftSize);int[]preRight=Arrays.copyOfRange(preorder,1+leftSize,preorder.length);int[]postLeft=Arrays.copyOfRange(postorder,0,leftSize);int[]postRight=Arrays.copyOfRange(postorder,leftSize,postorder.length-1);// 递归构建root.left=constructFromPrePost(preLeft,postLeft);root.right=constructFromPrePost(preRight,postRight);returnroot;}}

算法分析

  • 时间复杂度:O(n)

    • 每个节点被访问一次
    • 哈希表查找为 O(1)
    • 总体线性时间
  • 空间复杂度:O(n)

    • 哈希表:O(n)
    • 递归栈:O(h),其中 h 是树的高度
    • 最坏情况(链状树):O(n)

算法过程

1:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]

递归过程

第一层: - root = 1 - leftRoot = 2, 在postorder中索引 = 2 - leftSize = 2 - 0 + 1 = 3 - 左子树: pre=[2,4,5], post=[4,5,2] - 右子树: pre=[3,6,7], post=[6,7,3] 第二层(左子树): - root = 2 - leftRoot = 4, 在post中索引 = 0 - leftSize = 0 - 0 + 1 = 1 - 左子树: pre=[4], post=[4] - 右子树: pre=[5], post=[5] 第二层(右子树): - root = 3 - leftRoot = 6, 在post中索引 = 0 - leftSize = 0 - 0 + 1 = 1 - 左子树: pre=[6], post=[6] - 右子树: pre=[7], post=[7] 叶子节点直接返回

2:preorder = [1,2,3], postorder = [3,2,1]

递归过程

第一层: - root = 1 - leftRoot = 2, 在postorder中索引 = 1 - leftSize = 1 - 0 + 1 = 2 - 左子树: pre=[2,3], post=[3,2] - 右子树: pre=[], post=[] → null 第二层(左子树): - root = 2 - leftRoot = 3, 在post中索引 = 0 - leftSize = 0 - 0 + 1 = 1 - 左子树: pre=[3], post=[3] - 右子树: null 结果:1为根,2为左子,3为2的左子 1 / 2 / 3

测试用例

importjava.util.*;publicclassTest{// 前序遍历publicstaticList<Integer>preorderTraversal(TreeNoderoot){List<Integer>result=newArrayList<>();preorderHelper(root,result);returnresult;}privatestaticvoidpreorderHelper(TreeNodenode,List<Integer>result){if(node==null)return;result.add(node.val);preorderHelper(node.left,result);preorderHelper(node.right,result);}// 后序遍历publicstaticList<Integer>postorderTraversal(TreeNoderoot){List<Integer>result=newArrayList<>();postorderHelper(root,result);returnresult;}privatestaticvoidpostorderHelper(TreeNodenode,List<Integer>result){if(node==null)return;postorderHelper(node.left,result);postorderHelper(node.right,result);result.add(node.val);}publicstaticvoidmain(String[]args){Solutionsolution=newSolution();// 测试用例1:完整二叉树int[]pre1={1,2,4,5,3,6,7};int[]post1={4,5,2,6,7,3,1};TreeNoderoot1=solution.constructFromPrePost(pre1,post1);System.out.println("Test 1 Preorder: "+preorderTraversal(root1));// [1,2,4,5,3,6,7]System.out.println("Test 1 Postorder: "+postorderTraversal(root1));// [4,5,2,6,7,3,1]// 测试用例2:只有左子树int[]pre2={1,2,3};int[]post2={3,2,1};TreeNoderoot2=solution.constructFromPrePost(pre2,post2);System.out.println("Test 2 Preorder: "+preorderTraversal(root2));// [1,2,3]System.out.println("Test 2 Postorder: "+postorderTraversal(root2));// [3,2,1]// 测试用例3:只有右子树int[]pre3={1,2,3};int[]post3={2,3,1};TreeNoderoot3=solution.constructFromPrePost(pre3,post3);System.out.println("Test 3 Preorder: "+preorderTraversal(root3));// [1,2,3]System.out.println("Test 3 Postorder: "+postorderTraversal(root3));// [2,3,1]// 测试用例4:单个节点int[]pre4={1};int[]post4={1};TreeNoderoot4=solution.constructFromPrePost(pre4,post4);System.out.println("Test 4 Preorder: "+preorderTraversal(root4));// [1]System.out.println("Test 4 Postorder: "+postorderTraversal(root4));// [1]// 测试用例5:两个节点int[]pre5={1,2};int[]post5={2,1};TreeNoderoot5=solution.constructFromPrePost(pre5,post5);System.out.println("Test 5 Preorder: "+preorderTraversal(root5));// [1,2]System.out.println("Test 5 Postorder: "+postorderTraversal(root5));// [2,1]// 测试用例6:复杂树int[]pre6={1,2,4,5,3,6};int[]post6={4,5,2,6,3,1};TreeNoderoot6=solution.constructFromPrePost(pre6,post6);System.out.println("Test 6 Preorder: "+preorderTraversal(root6));// [1,2,4,5,3,6]System.out.println("Test 6 Postorder: "+postorderTraversal(root6));// [4,5,2,6,3,1]// 测试用例7:大数值int[]pre7={100,200,300};int[]post7={300,200,100};TreeNoderoot7=solution.constructFromPrePost(pre7,post7);System.out.println("Test 7 Preorder: "+preorderTraversal(root7));// [100,200,300]System.out.println("Test 7 Postorder: "+postorderTraversal(root7));// [300,200,100]}}

关键点

  1. 索引计算

    • 左子树大小 = leftRootPostIndex - postStart + 1
    • 右子树后序范围不包含最后一个元素(根节点)
  2. 边界条件

    • 空数组返回 null
    • 单元素数组直接返回节点
  3. 哈希表

    • 避免每次线性搜索后序数组
    • 将时间复杂度从 O(n²) 优化到 O(n)

常见问题

  1. 为什么不能唯一确定二叉树?

    • 例如:只有左子树 或 只有右子树,在前序和后序中表现相同
    • 前序:[1,2],后序:[2,1] 可以是 1->left=2 或 1->right=2
  2. 如果需要唯一?

    • 需要中序遍历作为额外信息
    • 前序+中序 或 中序+后序 可以唯一确定二叉树
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/4 8:09:46

YOLOv8模型生命周期管理:从训练到退役

YOLOv8模型生命周期管理&#xff1a;从训练到退役 在智能安防摄像头自动识别可疑行为、工业质检系统毫秒级发现产品缺陷的今天&#xff0c;目标检测早已不再是实验室里的概念验证。YOLO&#xff08;You Only Look Once&#xff09;系列作为实时检测领域的标杆&#xff0c;其最新…

作者头像 李华
网站建设 2026/2/15 3:22:33

Java毕设项目推荐-基于SpringBoot的云南旅游攻略信息系统的设计与实现基于springboot云南省旅游信息平台设计与实现【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/15 11:09:01

YOLOv8模型灰度阶段用户沟通策略:透明化推进

YOLOv8模型灰度阶段用户沟通策略&#xff1a;透明化推进 在AI产品从实验室走向真实场景的过程中&#xff0c;一个常被忽视却至关重要的环节是——如何让用户真正理解并信任你的模型&#xff1f; 以YOLOv8为例。作为当前目标检测领域最热门的框架之一&#xff0c;它凭借出色的推…

作者头像 李华
网站建设 2026/1/30 22:34:20

YOLOv8与Gloo Mesh集成实现跨集群调度

YOLOv8与Gloo Mesh集成实现跨集群调度 在智能制造、智慧交通和边缘视觉日益普及的今天&#xff0c;AI模型不再只是实验室里的“黑箱”&#xff0c;而是需要真正跑在成百上千台设备上、724小时持续服务的关键组件。一个训练好的YOLOv8模型如果只能部署在一个机房的一台GPU服务器…

作者头像 李华
网站建设 2026/2/16 1:02:58

跨平台调试总失败?教你3步精准定位C#应用崩溃根源

第一章&#xff1a;跨平台调试总失败&#xff1f;重新认识C#应用崩溃本质在开发C#跨平台应用时&#xff0c;开发者常遇到程序在目标环境莫名崩溃的问题。表面上看是运行时异常&#xff0c;但根源往往在于对崩溃机制的理解不足。.NET 应用的崩溃不仅与代码逻辑有关&#xff0c;更…

作者头像 李华
网站建设 2026/2/7 17:50:24

YOLOv8与Jaeger分布式追踪系统集成调试

YOLOv8与Jaeger分布式追踪系统集成调试 在现代AI系统的生产部署中&#xff0c;一个常见的困境是&#xff1a;模型在实验室环境下表现优异&#xff0c;但一旦上线&#xff0c;性能波动、延迟突增、错误频发等问题接踵而至。尤其在多租户、高并发的推理服务场景下&#xff0c;传统…

作者头像 李华