算法来自:
作者:数据结构和算法
链接: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的右边