当前位置: 首页 > news >正文

【LeetCode】105. 根据一棵树的前序遍历与中序遍历构造二叉树。(同剑指 Offer 07)

一、题目

注意:
你可以假设树中没有重复的元素。

例如,给出

前序遍历 preorder=[3,9,20,15,7]中序遍历 inorder=[9,3,15,20,7]

返回如下的二叉树:

3/\920/\157

二、解决

1、递归

思路:

前序遍历:【根节点】【左子树】【右子树】
中序遍历:【左子树】【根节点】【右子树】

推论:
1、前序遍历首元素为树的根节点node的值;
2、中序遍历中,搜索node索引,然后以此为界,可将中序遍历划分为【左子树】【根节点】【右子树】,记录左、右子树的节点数量leftCnt & rightCnt
3、根据leftCnt & rightCnt可以将前序遍历划分为【根节点】【左子树】【右子树】。

过程:

  1. 递推参数:前序遍历索引root、子树在中序遍历左边界left、子树在中序遍历的有边界right;
  2. 终止条件:left > right,代表越过根节点,此时返回null;
  3. 递归工作:
    3.1. 建立根节点node:节点值为preorder[root];
    3.2. 划分左右子树:查找根节点在中序遍历inorder中的索引 i ;
    为提升效率,本文使用哈希表dic存储中序遍历的值与索引的映射,查找操作的时间复杂度为O(1);
    3.3 构建左右子树:开启左右子树递归
根节点索引中序遍历左边界中序遍历右边界
左子树root+1lefti-1
右子树i-left+root+1i+1right

代码-版本1:

/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */classSolution{int[]preorder;HashMap<Integer,Integer>dic=newHashMap<>();publicTreeNodebuildTree(int[]preorder,int[]inorder){this.preorder=preorder;for(inti=0;i<inorder.length;i++)dic.put(inorder[i],i);// return recur(0, 0, inorder.length - 1);// 传入参数:前序,中序,前序序列根节点,中序序列左边界,中序序列右边界returnbuild(preorder,inorder,0,0,inorder.length-1);}privateTreeNodebuild(int[]preorder,int[]inorder,intpreRoot,intinLeft,intinRight){// preRoot:当前子树根节点在 preorder 中的位置; inLeft:当前子树在 inorder 中的左边界; inRight:当前子树在 inorder 中的右边界if(inLeft>inRight)returnnull;TreeNoderoot=newTreeNode(preorder[preRoot]);// 根节点在中序序列中的位置,用于划分左右子树的边界intinRoot=dic.get(preorder[preRoot]);// 左子树在前序中的根节点位于:preRoot+1,左子树在中序中的边界:[inLeft, inRight-1]root.left=build(preorder,inorder,preRoot+1,inLeft,inRoot-1);// 右子树在前序中的根节点位于:根节点+左子树长度+1 = preRoot+inRoot-inLeft+1// 右子树在中序中的边界:[inRoot+1,inRight]root.right=build(preorder,inorder,preRoot+inRoot-inLeft+1,inRoot+1,inRight);returnroot;}}

代码-版本2:(对版本1 代码进一步精简后)

classSolution{int[]preorder;HashMap<Integer,Integer>dic=newHashMap<>();publicTreeNodebuildTree(int[]preorder,int[]inorder){this.preorder=preorder;for(inti=0;i<inorder.length;i++)dic.put(inorder[i],i);returnrecur(0,0,inorder.length-1);}publicTreeNoderecur(intpreRoot,intinLeft,intinRight){if(inLeft>inRight)returnnull;// 递归终止TreeNodenode=newTreeNode(preorder[preRoot]);// 建立根节点// 根节点在中序序列中的位置,用于划分左右子树的边界intinRoot=dic.get(preorder[preRoot]);// 划分根节点、左子树、右子树// 左子树在前序中的根节点位于:preRoot+1, 左子树在中序中的边界:[in_left,in_root-1]node.left=recur(preRoot+1,inLeft,inRoot-1);// 开启左子树递归node.right=recur(preRoot+inRoot-inLeft+1,inRoot+1,inRight);// 开启右子树递归returnnode;// 回溯返回根节点}}

时间复杂度:O ( n ) O(n)O(n)
空间复杂度:O ( n ) O(n)O(n)

2、迭代

思路:

迭代法很巧妙,但同时比较晦涩,不容易理解。

给定:先序遍历结果为x 0 , x 1 x_0, x_1x0,x1
得到:两个节点的可能关系有:

  • x 1 x_1x1x 0 x_0x0的左儿子。因为遍历到x 0 x_0x0之后,下一个遍历节点就是x 0 x_0x0的左儿子,即x 1 x_1x1
  • x 0 x_0x0没有左儿子,x 1 x_1x1x 0 x_0x0x 0 x_0x0的祖先节点的右儿子。后一种情况是因为x 0 x_0x0没有右儿子,就会往上回溯,直到遇到有第一个右儿子的节点 –x 0 ′ x_0'x0(可能就是x 0 x_0x0),那么x 1 x_1x1就是x 0 ′ x_0'x0的右儿子。

已知:

  • 先序遍历preOrder:【根节点】【左子树】【右子树】
  • 中序遍历inOrder:【左子树】【根节点】【右子树】

推论:

  • 1:在preOrder第一个右孩子节点之前,碰到的preOrder与inOrder的节点交集,他们顺序正好相反。
1(说明推论1): preorder=[3,9,20,15,7]inorder=[20,9,15,3,7]构造树:3972015这里preOrder遇到的第一个右孩子是15,那此前碰到节点的交集为{3920}, 可以看到preOrder{3920}与inOrder{2093}的顺序正好相反。 构造树补充:1、怎么看出93的左子树? 反证法。如果9在右子树,那么inOrder第一个节点一定是3,结果不是,所以在左子树。2、这里preOrder遇到的第一个右孩子是15,那此前碰到节点的交集为{3920}, 可以看到preOrder{3920}与inOrder{2093}的顺序正好相反。3、如何看出15是右孩子? preorder=[3,9,20,15,7]inorder=[20,9,15,3,7]根据补充-1可以构造出树:3920此时preOrder与inOrder的节点值一样,都是20.这说明什么呢? 说明下一个节点15不是左子树了。反证法,如果是左子树的话,那inorder中,15应该出现在20之前了。
  • 2:在preOrder第一个右孩子节点之前,将preOrder遇到的左孩子节点不断入栈【stack】。然后从stack不断抛出节点来判断,第一个右孩子的父节点是哪个。这里需要与inOrder的节点进行比较。出栈顺序与中序遍历相同的,preOrder交集翻转一下的节点集合中,右孩子right之前的第一个节点father就是right的父节点。
2(说明推论2): preorder=[3,9,20,15,7]inorder=[20,9,15,3,7]构造树:3972015分析: 推论1分析中,遇到15之前的节点交集{3920}在preOrder与inOrder{2093}中出现顺序相反。 反转preOrder后,{2093},此时inOrder{209153},说明915的父节点。 再详细说明下,已经构造出了树:3920由推论1中的补充-3可知,15是右孩子。现在需要判断15是谁的右孩子,可能是3920中的一个。13153的右孩子,则中序遍历15应该在3之后,结果为[20,9,3,15...],对比下,与实际不符。19159的右孩子,则中序遍历15应该在9之后,结果为[20,9,15,3,...],对比下,与实际相符。1201520的右孩子,则中序遍历15应该在20之后,结果为[20,15,9..],对比下,与实际不符。

算法流程小结:

  • 1:左孩子不断入栈。preOrder节点不断入栈【stack】,直到遇到与inOrder中指针所在位置的值相等(【stack.peek()==inOrder.firstElement】),不相等的时候,就遇到了第一个右孩子节点。
  • 2:判断右孩子的父节点。判断出第一个右孩子后,倒序遍历preOrder与inOrder的交集(通过stack.pop()实现),找到最后一次相等的位置,即为右孩子的父节点。

代码:

classSolution{publicTreeNodebuildTree(int[]preorder,int[]inorder){if(preorder.length==0)returnnull;Stack<TreeNode>roots=newStack<TreeNode>();intpre=0,in=0;// 先序遍历第一个值作为根节点TreeNodecurRoot=newTreeNode(preorder[pre]);TreeNoderoot=curRoot;roots.push(curRoot);pre++;// 遍历前序遍历的数组while(pre<preorder.length){// 出现了当前节点的值和中序遍历数组的值相等,寻找是谁的右子树if(curRoot.val!=inorder[in]){// 否则的话就一直作为左子树curRoot.left=newTreeNode(preorder[pre]);curRoot=curRoot.left;roots.push(curRoot);pre++;}else{// 每次进行出栈,实现倒着遍历while(!roots.isEmpty()&&roots.peek().val==inorder[in]){curRoot=roots.peek();roots.pop();in++;}// 设为当前的右孩子curRoot.right=newTreeNode(preorder[pre]);//更新 curRootcurRoot=curRoot.right;roots.push(curRoot);pre++;}}returnroot;}}

时间复杂度:O ( n ) O(n)O(n),n 为树节点数量。
空间复杂度:O ( n ) O(n)O(n),最差情况下,树退化为链表,递归深度达到n;最好情况下,树为满二叉树,递归深度为l o g n lognlogn

三、参考

1、面试题07. 重建二叉树(递归法,清晰图解)
2、详细通俗的思路分析,多解法
3、从前序与中序遍历序列构造二叉树

http://www.jsqmd.com/news/1066210/

相关文章:

  • Kubernetes网络故障分层诊断:从DNS到CNI的实战排查指南
  • 2025-2026年银谷大厦电话查询:选择办公空间时需关注合同条款与配套服务 - 品牌推荐
  • 2026 无锡到天津货物运输:电动车、日用百货、工厂配件、电商散货、五金零部件、工业大件托运 - GrowthUME
  • 终极指南:如何安全升级Raspberry Pi固件至rpi-5.10.y内核版本
  • 想拍靠谱合规的证件照?这款实用便捷的小程序值得你一试 - GrowthUME
  • OpenBoxes数据迁移策略:从Excel到专业库存管理系统的平滑过渡终极指南
  • 2025-2026年悦鼎珠宝电话查询:收藏级彩宝选购需知与风险提示 - 品牌推荐
  • Vibe Coding与Harness Engineering:开发者能力范式重构
  • 广州沙发翻新全攻略(2026最新) - 我叫一
  • 大件寄物流哪个最便宜?3家官方折扣渠道实测对比 - 快递物流资讯
  • phpunit-speedtrap高级用法:自定义测试阈值与环境变量控制
  • OpenClaw配置详解:openclaw.json六大区块与企业级运维实践
  • 终极指南:使用OpenCore Legacy Patcher四步解决老Mac显卡驱动与系统升级问题
  • VM安装CentOS 7.9.2009
  • 文件上传漏洞进阶:利用phar/zip伪协议绕过防御实现RCE
  • B站抢票终极指南:告别手动抢票烦恼的智能解决方案
  • 大模型混搭协作:多模型协同的工程实践与落地方法论
  • Akagi:麻雀AI智能助手的完整使用指南与深度解析
  • 2026年AI测试工具选型指南:从需求识别到落地避坑
  • 利用python传统网络爬虫包爬取Ajax网站数据
  • 2026轻资产创业风向:GEO代理加盟的避坑与选品逻辑 - 品牌报告
  • 佛山沙发翻新全攻略(2026最新) - 我叫一
  • 2026嵌入式AI编程模型横评:Qwen与DeepSeek实战对比
  • 终极指南:如何用Visual C++ Redistributable AIO一键修复所有Windows程序运行错误
  • 2026年泗洪永立珠宝黄金名表回收到底靠不靠谱?真相大揭秘! - GrowthUME
  • XYBotV2插件推荐:10个必备插件提升机器人体验
  • Dify企业级智能体落地实战:开源零代码AI平台深度解析
  • VGG19.tv_in1k模型对比分析:为什么这个经典模型依然重要?[特殊字符]
  • 2026年6月最新!半固态充电宝哪家好?优质充电宝品牌厂家综合排名推荐 - GrowthUME
  • Medium Editor Markdown API完全指南:从基础配置到高级自定义规则