Leetcode105. 从前序与中序遍历序列构造二叉树 递归的优化和栈的实现方法的算法解析

算法来自:
作者:数据结构和算法
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvix0d/?discussion=qKNVpE
来源:力扣(LeetCode)


题目

从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
preorder 和 inorder 均 无重复 元素
inorder 均出现在 preorder
preorder 保证 为二叉树的前序遍历序列
inorder 保证 为二叉树的中序遍历序列

一、使用三个指针的递归

原po讲的已经很详细了,这里直接复制代码:

public TreeNode buildTree(int[] preorder, int[] inorder) {
    return helper(0, 0, inorder.length - 1, preorder, inorder);
}

public TreeNode helper(int preStart, int inStart, int inEnd, int[] preorder, int[] inorder) {
    if (preStart > preorder.length - 1 || inStart > inEnd) {
        return null;
    }
    //创建结点
    TreeNode root = new TreeNode(preorder[preStart]);
    int index = 0;
    //找到当前节点root在中序遍历中的位置,然后再把数组分两半
    for (int i = inStart; i <= inEnd; i++) {
        if (inorder[i] == root.val) {
            index = i;
            break;
        }
    }
    root.left = helper(preStart + 1, inStart, index - 1, preorder, inorder);
    root.right = helper(preStart + index - inStart + 1, index + 1, inEnd, preorder, inorder);
    return root;
}

作者:数据结构和算法
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvix0d/?discussion=qKNVpE
来源:力扣(LeetCode

思路是根据前序确定根节点,再在中序遍历中拆分出左子树和右子树,利用for循环找到下一个根节点(下一个根节点根据前序决定),继续拆分,当只剩一个点的时候无法再拆分(以左子树为例,index=instart时instart>index-1,return
null)

二、另一种递归方法

private int in = 0;
private int pre = 0;

public TreeNode buildTree(int[] preorder, int[] inorder) {
    return build(preorder, inorder, Integer.MIN_VALUE);
}

private TreeNode build(int[] preorder, int[] inorder, int stop) {
    if (pre >= preorder.length)
        return null;
    if (inorder[in] == stop) {
        in++;
        return null;
    }

    TreeNode node = new TreeNode(preorder[pre++]);
    node.left = build(preorder, inorder, node.val);
    node.right = build(preorder, inorder, stop);
    return node;
}

作者:数据结构和算法
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvix0d/?discussion=qKNVpE
来源:力扣(LeetCode

上一个算法我是用递归实现拆分来理解,所以看到这个算法时理解有点困难

首先看到xxx.left(),xxx.right的递归,可以想到二叉树的先序遍历
在这里插入图片描述
结合算法可以大概知道运行次序,首先我们取出先序遍历的第一个值创建新结点,然后调用递归函数求这个结点的左子树,在没有得到返回值之前,会一直调用这个递归函数

TreeNode node = new TreeNode(preorder[pre++]);
和将node.val作为递归参数int stop可实现这一步(stop值的条件判断先忽略)

根据那个图我们知道,当到node.val=5的node结点时应该返回,因为它的左子树为空(以left为例子)

//判断条件:
if (inorder[in] == stop) {
        in++;
        return null;

递归第n层:

TreeNode node = new TreeNode(preorder[pre++]);//假设node.val=5
node.left = build(preorder, inorder, node.val);
此时调用递归函数

递归第n+1层:
private TreeNode build(int[] preorder, int[] inorder, int stop) //stop=5

inorder[0]==5;in++;return null;
回到递归第n层

递归第n层:

//得到了结点值5的结点的左子树指向null,运行下一个语句
node.right = build(preorder, inorder, stop);//开始求结点的右子树

可以看到求left和求right的函数的第三个参数是不一样的,一个是node.val一个是stop
stop是node的父节点,数组inorder[in]的值要和node的父节点比较

当if()条件成立的时候return null == 当结点没有右子树
此时:node.right = build(preorder, inorder, stop)=null stop是函数的参数,是node
的父节点
假设此时结点5有右子树A,那么中序遍历为5A…(A的数组下标为1,if判断不成立)
假设没有右子树,那么根据中序遍历性质的左根右,那么下一个访问的就是5的父节点node.val=8。中序遍历是58…

所以right调用的递归函数参数是stop


三、使用栈解决

public TreeNode buildTree(int[] preorder, int[] inorder) {
    if (preorder.length == 0)
        return null;
    Stack<TreeNode> s = new Stack<>();
    //前序的第一个其实就是根节点
    TreeNode root = new TreeNode(preorder[0]);
    TreeNode cur = root;
    for (int i = 1, j = 0; i < preorder.length; i++) {
        //第一种情况
        if (cur.val != inorder[j]) {
            cur.left = new TreeNode(preorder[i]);
            s.push(cur);
            cur = cur.left;
        } else {
            //第二种情况
            j++;
            //找到合适的cur,然后确定他的右节点
            while (!s.empty() && s.peek().val == inorder[j]) {
                cur = s.pop();
                j++;
            }
            //给cur添加右节点
            cur = cur.right = new TreeNode(preorder[i]);
        }
    }
    return root;
}
作者:数据结构和算法
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvix0d/?discussion=qKNVpE
来源:力扣(LeetCode

在这里插入图片描述

这个算法可以理解为按先序遍历一步步构造出二叉树

1.n是m左子树结点的值=m左子树不为空

//第一种情况
        if (cur.val != inorder[j])

联系下图:
在这里插入图片描述在这里插入图片描述cur遍历前序数组,当cur.val!=inorder[j]时(cur.val=3),可以看出在中序数组中cur.val前面有数,说明结点具有左子树

//i初始值为1,是cur.val的下一个数,根据判断我们知道其是cur的左节点
//利用栈保存结点,因为还有右节点没有判断
 cur.left = new TreeNode(preorder[i]);
 s.push(cur);
 cur = cur.left;

2,n是m右子树节点的值或者是m某个祖先节点的右节点的值。
联系下图,前序遍历遍历到8:

 cur = cur.left;//cur:8,cur=cur.left:5

此时判读条件前序遍历所指向的=中序遍历所指向的结点值
寻找右节点安插位置的代码:

while (!s.empty() && s.peek().val == inorder[j]) {
cur = s.pop();
j++;
}

其实可以看出栈里面存放的是在前序遍历中遍历过的根节点,假设现在栈顶是结点8,此时j=1,inorder[1]=8,进行pop操作,栈顶为9,j++,inorder[2]=9
此时3!=2,2是9的右节点
在这里插入图片描述

这一步很难看出来是怎么实现找到右节点的

如何利用中序遍历判断,我们可以知道在中序遍历中记录着右节点2的根节点
但看前序9,8 ,5、中序5, 8,9 我们可以很快知道这个二叉树是长这样的:
在这里插入图片描述
可以看出2不可能是8的右子树,按栈先进后厨的顺序,pop出来和中序这段一样,所以pop到9的时候,cur指向9,循环结束,右节点2插在9的右边

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值