重建二叉树的循环解法

前言

纯循环的方法重建二叉树,无递归,放心阅读。
因在牛客网做剑指offer题时,简单看了下讨论区,发现都是递归方法,没找到循环解法,故研究了一下。
链接:https://www.nowcoder.com/questionTerminal/8a19cbe657394eeaac2f6ea9b0f6fcf6?f=discussion&toCommentId=8124951
原题:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路

写简单点,方便以后自己回来看能想起来。陈年老题估计也没人看,不用写太详细了。
前置条件:先序数组pre[]-中左右,中序数组in[]-左中右。
举例:pre[1,2,3,4,5,6,7],in[3,2,4,1,6,5,7].
示例二叉树图做如下表格:

pre1234567
in3241657
index(in)0123456

其中,index(in)代表数在in中的下标,如:1的index为:3.
我们重构二叉树时遍历节点的顺序同pre[](先序)顺序,按照左-中-右的顺序重构。
我们用一个stack存储循环遍历的节点,如下实例表格

stack123
index310

我们的目标是,建立一个节点与其**左子节点,父节点(省略,因为建立左或右子节点时就已对子节点建立了),右子节点(若存在的话)**的联系,已建立所有联系的节点即可在stack中排除,直到所有节点建立了联系,重构完成。把二叉树比作一个树网络,这个过程就是将节点放入网络并将其与相邻者之间建立连接网络的过程。

过程

因遍历的pre[]先序的顺序:中-左-右,可得出两种情况:1.新遍历到的节点(newNode)可能为stack.peek()(stack最外层的node)的左子节点;2.stack.peek()为某父节点的左侧节点(注意,未必是左子节点),而newNode为该父节点右子节点。
可根据index(node在in[]中的下标)判断属于哪种情况。
因in[]顺序为左-中-右,可知:若newNode的index比stack.peek()小,则代表newNode是stack.peek()的左子节点;

stack.peek().left = newNode;

stack.peek()与左子节点建立连接完成
若newNode的index比stack.peek()大(即index发生断层现象),则代表newNode是stack.peek()的右子节点。与右子节点建立连接完成

还记得前面目标中建立联系的顺序么?左叶节点,右叶节点。一个新node在刚开始遍历时,若为stack.peek()的左子节点,就已经建立连接了。当一个节点的右叶节点确立后,这个节点就已经全部连接建立完成,可以从stock中pop掉了。即代码中:

if(indexmap.get(newNode.val) > indexmap.get(stack.peek().val)){
    stack.peek().right = newNode;
    stack.pop();
}

这一结论的得出要结合下面的解释。

插播一个条件:
由前置条件的性质可推导出的条件:
1.在in[]中,index越靠前的val,在二叉树中越靠左;
这个看起来理所当然的简单条件是循环重建二叉树的重点。
由此可知,最左边(index=0)的node(数)只作为一个左叶节点,或一个没有左叶节点的分支节点。如节点1在下图中的位置:
图中,1节点为左叶节点图中,1节点为左叶节点。1节点为无左叶节点的分支节点图中,1节点为无左叶节点的分支节点。

现在回到原题,将newNode入栈。
以实例表格举例

stack123
index310

判断stack.peek()(原newNode)的index,当为0时(如表格中的节点3),若是插播条件中第一种情况,则因为前面2与左子节点newNode连接完成(3的父节点连接完成),而它没有左子节点,至于是否有右子节点(第二种情况),则看前一个节点(2节点)的index,若为1(0++),则为第一种情况(拿3,4个节点排列一下举例就知道了,比较简单,没时间解释),否则为第二种情况。第一种情况时,该节点没右子节点,即:父左右都确定了,连接完成,可以删去pop掉了。第二种情况则保留,在上面连接右子节点。同时每一次index匹配到时计数j++,到此关键部分已经简单解释完毕。

代码

    public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
       if (pre == null || pre.length == 0) {
            return null;
        }
        Map<Integer, Integer> indexmap = new HashMap<>();
        for(int i=0;i< in.length; i++){
            indexmap.put(in[i], i);
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = null;
        for (int i = 0, j = 0; i < pre.length; i++) {
            TreeNode newNode = new TreeNode(pre[i]);
            if(i==0){
                node = newNode;
            }
            if(!stack.isEmpty()){
                if(indexmap.get(newNode.val) > indexmap.get(stack.peek().val)){
                    stack.peek().right = newNode;
                    stack.pop();
                }else{
                    stack.peek().left = newNode;
                }
            }
            stack.push(newNode);
            TreeNode tmp = null;
            while (!stack.isEmpty() && stack.peek().val == in[j]) {
                tmp = stack.pop();
                j++;
            }
            if (tmp != null) {
                stack.push(tmp);
            }
        }
        return node;
    }

注:若有解释不清楚之处,可交流。已通过牛客网剑指offer编程题JZ4重建二叉树的所有测试用例,若万一发现有错误,请告知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值