输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:3
/ \
9 20
/ \
15 7来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof
解题思路:
前序遍历性质: 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序。
中序遍历性质: 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序。
以题目示例为例:
前序遍历划分 [ 3 | 9 | 20 15 7 ]
中序遍历划分 [ 9 | 3 | 15 20 7 ]
根据以上性质,可得出以下推论:
前序遍历的首元素 为 树的根节点 node 的值。
在中序遍历中搜索根节点 node 的索引 ,可将 中序遍历 划分为 [ 左子树 | 根节点 | 右子树 ] 。
根据中序遍历中的左 / 右子树的节点数量,可将 前序遍历 划分为 [ 根节点 | 左子树 | 右子树 ] 。
贴张图:
class Solution_07 {
public class ReBuildTree {
private int[] po;
private int[] last;
private Map<Integer,Integer> dict;
//前序和中序重构二叉树
public TreeNode buildTree(int[] preOrder, int[] inOrder){
if(preOrder == null || inOrder == null || preOrder.length == 0|| inOrder.length == 0 || preOrder.length != inOrder.length){
return null;
}
po = preOrder;
dict = new HashMap<Integer,Integer>(inOrder.length);
for(int i = 0; i < inOrder.length; i++){
dict.put(inOrder[i],i);
}
return reBuildTree(0, 0, inOrder.length - 1);
}
private TreeNode reBuildTree(int preRoot, int istart, int iend) {
if(istart > iend){
return null;
}
TreeNode treeNode = new TreeNode(po[preRoot]);
int i = dict.get(po[preRoot]);
int leftLen = i - istart; //计算左子树长度,左子树的长度等于中序根节点的下标-中序起始节点的下标
treeNode.left = reBuildTree(preRoot + 1, istart, i -1);
treeNode.right = reBuildTree(preRoot + 1 + leftLen, i + 1, iend);
return treeNode;
}
//后序和中序重构二叉树
public TreeNode buildTreeByLast(int[] postOrder, int[] inOrder){
if(postOrder == null || inOrder == null || postOrder.length == 0 || inOrder.length == 0 || postOrder.length != inOrder.length){
return null;
}
po = postOrder;
dict = new HashMap<Integer, Integer>(inOrder.length);
for(int i = 0; i < inOrder.length; i++){
dict.put(inOrder[i], i);
}
return reBuildTreeByLast(postOrder.length - 1, 0, inOrder.length - 1);
}
private TreeNode reBuildTreeByLast(int postRoot, int istart, int iend) {
if(istart > iend || postRoot < 0){
return null;
}
TreeNode treeNode = new TreeNode(po[postRoot]);
int i = dict.get(po[postRoot]);
int rightLen = iend - i; //计算右子树的长度
treeNode.left = reBuildTreeByLast(postRoot - rightLen - 1, istart, i - 1);
treeNode.right = reBuildTreeByLast(postRoot - 1, i + 1, iend);
return treeNode;
}
}
}
比较难理解的点:
前序+中序重建二叉树递归函数中的参数含义:
“i”:根节点在中序遍历中的坐标
“preRoot + 1” :先序根节点坐标 + 1,即下次递归的左子树根节点坐标,也即左子树的起始坐标。
“preRoot + (i - istart)”:表示先序根节点坐标 + (中序根节点坐标 - 中序起始坐标)=左子树起始坐标 + (左子树长度),即前序遍历中左子树的终止坐标。
“preRoot + 1 + (i - istart) ”:同理即左子树起始坐标 + 1 +(左子树长度),即右子树的根节点坐标(右子树的起始坐标)。
后序+中序重建二叉树基本与前序+中序一样,唯一不同的点是:
因为后序是从尾部开始的,所以在计算递归左子树时,需要用根节点坐标 - 右子树的长度 - 1 得到下次递归的左子树根节点的起始坐标。
知识点补充:
二叉树遍历
方法一:递归遍历
常用的一种方法。
方法二:利用栈迭代法
在利用栈遍历时,添加null是为了添加访问标记,当遇到null时,表示下一个节点即为要访问的节点,直接跳过null,弹出下一个节点即可。其实在前序遍历时,null的意义并不大,可以不加,只是为了前序,中序,后序的统一。
参考了该博主的解答
以下为两种方法的Java实现:
import java.util.Stack;
class TreeNode{
private int value;
TreeNode left;
TreeNode right;
public TreeNode(int value){
this.value = value;
}
@Override
public String toString() {
return "TreeNode{" +
"value=" + value +
'}';
}
/*
递归先序遍历二叉树
*/
public void preOrder(){
System.out.println(this);
if(this.left != null){
this.left.preOrder();
}
if(this.right != null){
this.right.preOrder();
}
}
/*
利用栈前序遍历二叉树
*/
public void preOrderByStack(){
System.out.println("利用栈前序遍历二叉树:");
Stack<TreeNode> stack = new Stack<>();
stack.push(this);
while(!stack.isEmpty()){
TreeNode treeNode = stack.pop();
if(treeNode != null) {
if (treeNode.right != null) {
stack.push(treeNode.right);
}
if(treeNode.left != null){
stack.push(treeNode.left);
}
stack.push(treeNode);
stack.push(null);//加入访问标志
}else{
System.out.println(stack.pop());
}
}
}
/*
递归中序遍历二叉树
*/
public void inOrder(){
if(this.left != null){
this.left.inOrder();
}
System.out.println(this);
if(this.right != null){
this.right.inOrder();
}
}
/*
利用栈中序遍历二叉树
*/
public void inOrderByStack(){
System.out.println("利用栈中序遍历二叉树:");
Stack<TreeNode> stack = new Stack<>();
stack.push(this);
while(!stack.isEmpty()){
TreeNode treeNode = stack.pop();
if(treeNode != null){
if(treeNode.right != null){
stack.push(treeNode.right);
}
stack.push(treeNode);
stack.push(null);//加入访问标志
if(treeNode.left != null){
stack.push(treeNode.left);
}
}else{
System.out.println(stack.pop());
}
}
}
/*
递归后序遍历二叉树
*/
public void postOrder(){
if(this.left != null){
this.left.postOrder();
}
if(this.right != null){
this.right.postOrder();
}
System.out.println(this);
}
/*
利用栈后序遍历二叉树
*/
public void postOrderByStack(){
System.out.println("利用栈后序遍历二叉树:");
Stack<TreeNode> stack = new Stack<>();
stack.push(this);
while(!stack.isEmpty()){
TreeNode treeNode = stack.pop();
if(treeNode != null){
stack.push(treeNode);
stack.push(null);//加入访问标志
if(treeNode.right != null){
stack.push(treeNode.right);
}
if(treeNode.left != null){
stack.push(treeNode.left);
}
}else{
System.out.println(stack.pop());
}
}
}
}