剑指offer-4、重建二叉树

题⽬描述

输⼊某⼆叉树的前序遍历和中序遍历的结果,请重建出该⼆叉树。假设输⼊的前序遍历和中序遍历的结果中都不含重复的数字。例如输⼊前序遍历序列{1,2,4,7,3,5,6,8} 和中序遍历序列{4,7,2,1,5,3,8,6} ,则重建⼆叉树并返回。

思路及解答

递归解决

看上⾯的图⽚,⾸先数据保证了正确性,那么前序的第⼀个肯定是root 节点,也就是1 ,那么就需要在中序遍历中找到1 的位置,左边就是这个root 的左⼦树,右边就是root 的右⼦树。

举个例子:对根节点的左⼦树进⾏解析:

对右⼦树进⾏解析:

只需要不断递归即可,当边界左边⼤于右边的时候,则停⽌。

java 复制代码
```java
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
	public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
		if (pre == null || pre.length == 0 || in == null || in.length == 0) {
			return null;
		}
		
		TreeNode root = constructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length-1);
		return root;
		}

		TreeNode constructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
		// 不符合条件直接返回null
		if (startPre > endPre || startIn > endIn) {
			return null;
		}

		// 构建根节点
		TreeNode root = new TreeNode(pre[startPre]);
		for (int index = startIn; index <= endIn; index++) {
			if (in[index] == pre[startPre]) {
			// 左⼦树
			root.left = constructBinaryTree(pre, startPre + 1, startPre + (index - startIn), in, startIn, index - 1);
			// 右⼦树
			root.right = constructBinaryTree(pre, (index - startIn) + startPre + 1, endPre, in, index + 1, endIn);
			break;
			}
		}
		return root;
	}
}
```

栈解法

所有的递归理论上都可以⽤栈模拟,那么我们如何⽤栈解答呢?

我们可以⼀开始创建⼀个栈,分别⽤两个指针执⾏前序遍历和中序遍历的第⼀个元素,先将前序遍历的第⼀个元素压⼊栈中,因为前序遍历的特性,第⼀个元素肯定是根节点。

开始循环,对⽐栈顶的元素和中序遍历数组的元素

  1. 如果不相等,说明当前栈顶元素还有左⼦树,因为如果没有左⼦树的话,前序的第⼀个元素和中序的第⼀个元素应该相等。既然有左⼦树,那么前序遍历指针指向的元素就是它的左⼦树根节点,建⽴关系,压栈。
  2. 如果相等,那么说明当前的栈顶元素已经没有左⼦树了。
    1. 把栈顶元素和中序遍历的元素对⽐,相等则弹出之后,继续对⽐下⼀个元素与当前的栈顶元素,直到不相等为⽌。(相等说明没有右节点,弹出以为着退出上⼀层)
    2. 不相等的时候,需要把当前的元素作为右叶⼦节点,压⼊栈中。

上⾯的⽂字可能⽐较难懂,但是不紧要,下⾯图⽂说明:

⾸先我们有前序和中序遍历的数组,原树结构⼤致了解⼀下:

把当前的前序遍历的元素 1 先放到栈⾥⾯,这个肯定是根节点:

对⽐中序遍历第⼀个元素 4 ,和栈顶元素 1 ,不相等,那么说明有左⼦树,前序遍历的第 2 个元素 2就是左⼦树节点,关联成左⼦树,压栈:

同样得不相等,会持续压栈:

直到,中序遍历的第⼀个元素 4 ,已经等于栈顶元素 4 ,说明4 没有左⼦树了,因为 4 是在中序遍历⾥⾯,中序遍历完根节点,剩下的部分只能是右⼦树。

那么把 4 弹出去,中序遍历指针移动到下⼀个位置:

这个时候, 7 肯定是之前节点 4 的右⼦树节点,那么关联关系之后,压⼊栈⾥⾯:

此时,结束了⼀次循环,注意前序遍历的指针会往后移动⼀位。

再次循环的时候,依然判断中序遍历中的数值是否等于栈顶元素,发现都是7 ,相等。弹出,移动到下⼀个位置,相当于退出了上⼀层:

依旧 2==2 相等,再次弹出:

同样中序遍历的 1 还是等于栈顶的 1 ,弹出,移动到下⼀位:

这个时候,栈顶元素的 1 已经被取出来了,说明左⼦树全部遍历完成了,剩下的部分是它的右⼦树内容了,那么前序遍历中, 3 就必定是根节点1 的右⼦树的根,压⼊栈中,前序遍历索引指向下⼀个元素:

到这⾥其实是结束了第⼆轮的循环。

再次循环,判断中序遍历的数值和栈顶元素不相等,那么说明是左⼦树,前序遍历中的 5 压⼊栈内,索引后移:

中序遍历的数值和栈顶元素⼀对⽐,发现相等,说明5 没有左⼦树了,弹出,索引后移:

依然 两个都是 3(说明 3 的左⼦树被遍历完成了,剩下的是 3 的右⼦树了),继续弹出,后移

此时, 3 是刚刚弹出的元素,剩下的元素都是它的右⼦树,那么前序遍历中指向的数组6 就是3 的右⼦树, 6 压⼊栈中:

对⽐栈顶元素6 和中序遍历中的8 发现不相等,那么把前序遍历中的 8 压栈,成为左⼦树:

对⽐栈顶元素 8 和中序遍历的 8 ,相等则弹出:

还是相等,继续弹出:

栈⾥⾯没有元素,并且数组都遍历结束,整个过程结束。

java 复制代码
import java.util.Stack;
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) {
    	val = x;
    }
}
	
public class Solution7 {
    public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        // 判空
        if (pre == null || pre.length == 0 || in == null || in.length == 0) {
        	return null;
        }
        
        Stack<TreeNode> stack = new Stack<>();
        int preIndex = 0;
        int inIndex = 0;
        TreeNode root = new TreeNode(pre[preIndex]);
        stack.push(root);
        
        for (preIndex = 1; preIndex < pre.length; preIndex++) {
            TreeNode node = stack.peek();
            // 不相等说明还有左⼦树
            if (node.val != in[inIndex]) {
                // 关联成为左⼦树,压栈
                node.left = new TreeNode(pre[preIndex]);
                stack.push(node.left);
            } else {
            	// 相等说明,当前节点没有左⼦树
                while (!stack.isEmpty() && stack.peek().val == in[inIndex]) {
                    // 只要两者相等,说明没有右⼦树,弹出节点,退到上⼀层
                    node = stack.pop();
                    inIndex++;
                }
                // 有右⼦树,关联
                node.right = new TreeNode(pre[preIndex]);
                stack.push(node.right);
            }
        }
        return root;
    }
}
相关推荐
我命由我123452 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
武子康4 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
YuTaoShao7 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw7 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨7 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
双力臂4048 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
Edingbrugh.南空8 小时前
Aerospike与Redis深度对比:从架构到性能的全方位解析
java·开发语言·spring
QQ_4376643149 小时前
C++11 右值引用 Lambda 表达式
java·开发语言·c++
永卿0019 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式
誰能久伴不乏9 小时前
Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
java·服务器·前端