给你前序中序遍历,让你重建二叉树,听说这很难?

说实话,挺难的。
我一开始接触到这道题真的一头雾水,虽然老师在课上讲过,但听得半知半解。在经过我半天的消化吸收之后,基本了解了这个问题的解法。这这个问题有两种考法:1、给定前序和中序遍历,重建二叉树。2、给定后序和中序遍历,重建二叉树。若给的是前序和后序遍历,抱歉,臣妾做不到。。。
这两种考法思路是一样的,会一种另一种也就拿下了。以这道题为例

剑指 Offer 07. 重建二叉树

题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
3
/ \
9 20
. / \
15 7

题目难度:medium
解题思路:利用先序遍历和中序遍历的特点。首先要明确先序遍历和中序遍历的特点。先序遍历,其根节点必定在遍历的第一个位置,如 [root, [left], [right]],但是不知道左子树 [left] 和右子树 [right] 的分界点。而中序遍历中,只知道其根节点左边是左子树,右边是右子树,但是不知道根节点的具体位置,如 [[left], root, [right]],这里root的下标是不清楚的。那么,该如何利用这些信息来重建二叉树呢?

  1. 重建二叉树,最开始要干嘛?当然是确定整棵二叉树的根节点啦!以 preorder=[3,9,20,15,7],inorder=[9,3,15,20,7] 为例。
    我们知道,前序遍历的第一个位置就是树的根节点,所以,本例中的根节点就是 3 。但是,我们从前序遍历preorder中仅仅只能知道第一个元素是根节点,左右子树还是无法确定,所以需要在中序遍历中才能确定左右子树。仅仅确定3是根节点
  2. 由于中序遍历中,根节点的左边全部都是左子树,右边全是右子树,形如【[left], root, [right]】。所以,只要知道根节点的位置那就能确定这颗二叉树的左子树和右子树了。在第一步中,我们从前序遍历中知道了根节点为3,那么我们只要在中序遍历找到根节点位置,那么根节点左侧就是左子树,右侧就是右子树。在例子中,根节点在中序遍历inorder中的位置下标为1,也就是说左子树为[9],右子树为[15,20,7],分别确定了左子树和右子树,其中左子树的l_length长度为1,这个很重要。
    在这里插入图片描述

然后我们得到了基本的二叉结构如下:
基本二叉结构
3. 到这一步我们已经确定了根节点和它的左右子树,接下来就是递归地重建它的左右子树,基本步骤与之前差不多,不过需要分别确定左子树的根节点和右子树的根节点。在前序遍历中,左子树的根节点就在整棵树的根节点的后面,即left_root=root+1。由于本例中左子树只有一个元素,所以左子树已经重建完毕。接下来重建右子树。首先还是确定右子树的根节点,要找到右子树根节点,还是必须在前序遍历中找。前面我们知道,整棵树的根节点是preorder的第一个元素,那么右子树的根节点又在哪呢?根据前序遍历的框架preorder=【[root],[left],[right]】我们知道,右子树的第一个元素就是其根节点,且其位置关系满足right_root = root + 左子树长度 + 1,所以本例中右子树的根节点位置为right_root = 0 + 1 + 1,也就是preorder[2],即节点20 。这样我们就确定了右子树的根节点位置,然后递归地求解这个子树的左右子树即可。
右子树的根节点位置
最终重建完成的二叉树

总结一下,根据前序和中序遍历重建二叉树的关键有,1 、利用前序遍历确定根节点,2、利用中序遍历确定根节点的左右子树,同时记录左子树长度以求得右子树的根节点在前序遍历中的位置。如果给的后序遍历和中序遍历,基本思路也是这样,不过就是确定根节点的时候是后序遍历数组的最后一个元素。
示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //若前序遍历和中序遍历长度不一致,说明不可能重建成功
        if (preorder.size() != inorder.size()){
            return NULL;
        }
        int size = preorder.size();
        //先把中序遍历的位置记录下来,用于快速查找根节点位置
        for (int i = 0; i < inorder.size(); i++){
            m[inorder[i]] = i;
        }
        return build(preorder, 0, size-1, inorder, 0, size-1);
    }
    
    TreeNode* build(const vector<int>& preorder, int preorder_left, int preorder_right, const vector<int>& inorder, int inorder_left, int inorder_right){
    	//当左边界超出右边界时,说明已经重建完毕了
        if (preorder_left > preorder_right){
            return NULL;
        }
        int preorder_root = preorder_left; //前序遍历第一个节点就是根节点
        int inorder_root = m[preorder[preorder_root]]; //在中序遍历中找到根节点所在位置

        TreeNode* root = new TreeNode(preorder[preorder_root]); //建立本子树的根节点

        int left_subtree_size = inorder_root - inorder_left; //确定左子树长度

        //递归构造左子树,并连接到根节点,此时左子树在preorder的范围为[左边界+1:左边界+左子树长度),左闭右开。
        //在inorder中的范围为[inorder根节点位置-左子树长度:inorder根节点位置-1)
        root->left = build(preorder, preorder_left+1, preorder_left+left_subtree_size, inorder,inorder_root-left_subtree_size, inorder_root-1);

        //递归构造右子树,并连接到根节点
        //先序遍历中[preorder左边界+1+左子树长度:preorder右边界]的元素对应了中序遍历中[根节点位置+1:inorder右边界]的元素
        root->right = build(preorder, preorder_left+1+left_subtree_size, preorder_right, inorder, inorder_root+1, inorder_right);
        return root;
    }
    private:
    unordered_map<int, int> m;
};

参考

剑指offer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值