从前序与中序遍历序列构造二叉树( LeetCode 105 )

一、题目描述

给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。

二、题目解析

三、参考代码

1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 从前序与中序遍历序列构造二叉树( LeetCode 105 ):https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {

        // 题目中说 preorder 和 inorder 均无重复元素
        // 通过哈希表把中序遍历序列中的值和顺序建立映射关系
        HashMap<Integer, Integer> map = new HashMap<>();

        // 通过 for 循环,遍历完中序遍历序列中的所有元素
        for (int i = 0; i < inorder.length; i++) {
             // 以中序序列中的元素值 inorder[i] 作为 key
             // 以位置 i 作为 value
             // 存放到哈希表中
             // 比如中序遍历序列中的元素是 [   A  ,   D  ,   E  ,   F  ,   G  ,   H  ,   M  ,   Z  ]
             // 那么这个哈希表就是以下的样子
             // | 索引 | 位置  |
             // | A | 0  |
             // | D | 1  |
             // | E | 2  | 
             // | F | 3  | 
             // | G | 4  | 
             // | H | 5  | 
             // | M | 6  | 
             // | Z | 7  |            
             map.put(inorder[i], i);
        }

        // 下面开始构建二叉树
        // 把前序遍历序列中的第一个元素 preorder[0] 作为二叉树的根节点
        // 因为任意二叉树的前序遍历序列中的第一个元素,一定是二叉树的根节点
        TreeNode root = new TreeNode(preorder[0]);


        // 继续遍历前序遍历序列中的其它元素
        for(int i = 1 ; i < preorder.length ; i++){

            // 把当前遍历的元素构造为一个二叉树的节点
            TreeNode node = new TreeNode(preorder[i]);

            // 把构造的节点插入到以 root 为根节点的二叉树中
            insertNode(root,node,map);

        }

        // 当 preorder 中所有元素都构造并且插入完毕之后
        // 二叉树就完成了构建
        return root;


    }

    // root : 二叉树的根节点
    // node : 待插入的节点
    // map : 节点值和中序遍历序列位置的映射
    private void insertNode(TreeNode root,TreeNode node,HashMap<Integer,Integer> map){

            // 当 root 和 node 指向的节点相同时,跳出循环
            while(root != node){

                // 如果 node 的中序遍历序列位置小于 root 的中序遍历序列位置
                // 说明 node 应该在 root 的左子树中
                if(map.get(node.val) < map.get(root.val)){

                    // 如果此时 root 没有左子树
                    if(root.left == null){
                        // 那么就把 node 设置为 root 的左子树
                        root.left = node;
                    }

                    // 1、如果之前 root 没有左子树,那么通过上面的代码,就设置好了 root 的左子树
                    // 也就是说 node 是 root 的一个叶子节点,完成了插入操作
                    // 把 root 指向 root.left 后,root 为 node,跳出了循环

                    // 2、如果之前 root 已经有左子树,那么就不能直接把 node 插入到 root 的左子树上
                    // 需要判断应该把 node 插入到 root 左子树上的孩子节点的那个位置上
                    // 比如现在的 root 是这个样子,node 为 A
                    //        G
                    //       /
                    //      D
                    //     /  \
                    //    ①   ②
                    // root 已经有左子树 D 了,所以 node 应该考虑插入到 D 中的 ① 还是 ② 上面
                    // 所以,把 root 指向 root.left ,继续遍历 root 的左子树的情况
                    root = root.left;


                }else{

                    // 如果 node 的中序遍历序列位置大于 root 的中序遍历序列位置
                    // 说明 node 应该在 root 的右子树中

                    // 如果此时 root 没有右子树
                    if(root.right == null){
                        // 那么就把 node 设置为 root 的右子树
                        root.right = node;
                    }

                    // 把 root 指向 root.right ,继续遍历 root 的右子树的情况
                    root = root.right;

                }

            }
    }
}

2、C++ 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 从前序与中序遍历序列构造二叉树( LeetCode 105 ):https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
class Solution {

    // 题目中说 preorder 和 inorder 均无重复元素
    // 通过哈希表把中序遍历序列中的值和顺序建立映射关系
    unordered_map<int, int> map;

public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {

        // 通过 for 循环,遍历完中序遍历序列中的所有元素
        for (int i = 0; i < inorder.size(); i++) {
             // 以中序序列中的元素值 inorder[i] 作为 key
             // 以位置 i 作为 value
             // 存放到哈希表中
             // 比如中序遍历序列中的元素是 [   A  ,   D  ,   E  ,   F  ,   G  ,   H  ,   M  ,   Z  ]
             // 那么这个哈希表就是以下的样子
             // | 索引 | 位置  |
             // | A | 0  |
             // | D | 1  |
             // | E | 2  | 
             // | F | 3  | 
             // | G | 4  | 
             // | H | 5  | 
             // | M | 6  | 
             // | Z | 7  |            
             map[inorder[i]] =  i;
        }

        // 下面开始构建二叉树
        // 把前序遍历序列中的第一个元素 preorder[0] 作为二叉树的根节点
        // 因为任意二叉树的前序遍历序列中的第一个元素,一定是二叉树的根节点
        TreeNode *root = new TreeNode(preorder[0]);

        // 继续遍历前序遍历序列中的其它元素
        for(int i = 1 ; i < preorder.size() ; i++){

            // 把当前遍历的元素构造为一个二叉树的节点
            TreeNode *node = new TreeNode(preorder[i]);

            // 把构造的节点插入到以 root 为根节点的二叉树中
            insertNode(root,node);

        }

        // 当 preorder 中所有元素都构造并且插入完毕之后
        // 二叉树就完成了构建
        return root;
    }

    // root : 二叉树的根节点
    // node : 待插入的节点
private:
       void insertNode(TreeNode *root,TreeNode *node){

            // 当 root 和 node 指向的节点相同时,跳出循环
            while(root != node){

                // 如果 node 的中序遍历序列位置小于 root 的中序遍历序列位置
                // 说明 node 应该在 root 的左子树中
                if(map[node->val] < map[root->val]){

                    // 如果此时 root 没有左子树
                    if(root->left == NULL){
                        // 那么就把 node 设置为 root 的左子树
                        root->left = node;
                    }

                    // 1、如果之前 root 没有左子树,那么通过上面的代码,就设置好了 root 的左子树
                    // 也就是说 node 是 root 的一个叶子节点,完成了插入操作
                    // 把 root 指向 root.left 后,root 为 node,跳出了循环

                    // 2、如果之前 root 已经有左子树,那么就不能直接把 node 插入到 root 的左子树上
                    // 需要判断应该把 node 插入到 root 左子树上的孩子节点的那个位置上
                    // 比如现在的 root 是这个样子,node 为 A
                    //        G
                    //       /
                    //      D
                    //     /  \
                    //    ①   ②
                    // root 已经有左子树 D 了,所以 node 应该考虑插入到 D 中的 ① 还是 ② 上面
                    // 所以,把 root 指向 root.left ,继续遍历 root 的左子树的情况
                    root = root->left;


                }else{

                    // 如果 node 的中序遍历序列位置大于 root 的中序遍历序列位置
                    // 说明 node 应该在 root 的右子树中

                    // 如果此时 root 没有右子树
                    if(root->right == NULL){
                        // 那么就把 node 设置为 root 的右子树
                        root->right = node;
                    }

                    // 把 root 指向 root.right ,继续遍历 root 的右子树的情况
                    root = root->right;

                }

            }
    }
};

3、Python 代码

# 登录 AlgoMooc 官网获取更多算法图解
# https://www.algomooc.com
# 作者:程序员吴师兄
# 代码有看不懂的地方一定要私聊咨询吴师兄呀
# 从前序与中序遍历序列构造二叉树( LeetCode 105 ):https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal
class Solution:

    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:

        # 题目中说 preorder 和 inorder 均无重复元素
        # 通过哈希表把中序遍历序列中的值和顺序建立映射关系
        # 通过 for 循环,遍历完中序遍历序列中的所有元素
        map = dict()

        for i in range(len(inorder)) :
             # 以中序序列中的元素值 inorder[i] 作为 key
             # 以位置 i 作为 value
             # 存放到哈希表中
             # 比如中序遍历序列中的元素是 [   A  ,   D  ,   E  ,   F  ,   G  ,   H  ,   M  ,   Z  ]
             # 那么这个哈希表就是以下的样子
             # | 索引 | 位置  |
             # | A | 0  |
             # | D | 1  |
             # | E | 2  | 
             # | F | 3  | 
             # | G | 4  | 
             # | H | 5  | 
             # | M | 6  | 
             # | Z | 7  |  
            map[inorder[i]] =  i


        # 下面开始构建二叉树
        # 把前序遍历序列中的第一个元素 preorder[0] 作为二叉树的根节点
        # 因为任意二叉树的前序遍历序列中的第一个元素,一定是二叉树的根节点
        root =  TreeNode(preorder[0])


        # 继续遍历前序遍历序列中的其它元素
        for i in range( 1 ,len(preorder)):

            # 把当前遍历的元素构造为一个二叉树的节点
            node = TreeNode(preorder[i])

            # 把构造的节点插入到以 root 为根节点的二叉树中
            self.insertNode(root,node,map)



        # 当 preorder 中所有元素都构造并且插入完毕之后
        # 二叉树就完成了构建
        return root

    # root : 二叉树的根节点
    # node : 待插入的节点
    def insertNode(self , root : TreeNode, node : TreeNode,map:dict) :
            # 当 root 和 node 指向的节点相同时,跳出循环
            while root != node : 

                # 如果 node 的中序遍历序列位置小于 root 的中序遍历序列位置
                # 说明 node 应该在 root 的左子树中
                if map[node.val] < map[root.val] : 

                    # 如果此时 root 没有左子树
                    if not root.left : 
                        # 那么就把 node 设置为 root 的左子树
                        root.left = node


                    # 1、如果之前 root 没有左子树,那么通过上面的代码,就设置好了 root 的左子树
                    # 也就是说 node 是 root 的一个叶子节点,完成了插入操作
                    # 把 root 指向 root.left 后,root 为 node,跳出了循环

                    # 2、如果之前 root 已经有左子树,那么就不能直接把 node 插入到 root 的左子树上
                    # 需要判断应该把 node 插入到 root 左子树上的孩子节点的那个位置上
                    # 比如现在的 root 是这个样子,node 为 A
                    #        G
                    #       /
                    #      D
                    #     /  \
                    #    ①   ②
                    # root 已经有左子树 D 了,所以 node 应该考虑插入到 D 中的 ① 还是 ② 上面
                    # 所以,把 root 指向 root.left ,继续遍历 root 的左子树的情况
                    root = root.left


                else:

                    # 如果 node 的中序遍历序列位置大于 root 的中序遍历序列位置
                    # 说明 node 应该在 root 的右子树中

                    # 如果此时 root 没有右子树
                    if not root.right : 
                        # 那么就把 node 设置为 root 的右子树
                        root.right = node


                    # 把 root 指向 root.right ,继续遍历 root 的右子树的情况
                    root = root.right

四、动画理解(没有声音)

发表评论

邮箱地址不会被公开。 必填项已用*标注