LeetCode题解—重建二叉树

简介: 今天继续二叉树相关的算法题

前言


今天继续二叉树相关的算法题


题目


输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。


例如,给出


前序遍历 preorder = [3,9,20,15,7]


中序遍历 inorder = [9,3,15,20,7]


返回如下的二叉树:


3
   / \
  9  20
    /  \
   15   7


题解


上周说过 前序遍历和后序遍历,其实这种前序、中序、后序都是相对于中间节点的处理顺序。


比如先序遍历的顺序就应该是:


[ 根节点 | 左子树 | 右子树 ]


同理,中序遍历的顺序就是:


[ 左子树 | 根节点 | 右子树 ]


所以前序遍历的第一个元素肯定就是 根节点。


然后,我们就能在 中序遍历找到根节点,并正确把中序遍历区分为三部分,也就是左子树中序、根节点、右子树中序。


举个例子:


如果只有三个元素,那么到这里就能结束了,因为树已经能画出来了。


比如前序是【3,9,20】,中序是【9,3,20】


我们根据中序知道了根节点3,然后在中序中就能区分出左子树节点9,根节点3,右子树节点20。


现在我们扩散开,如果不止3个节点呢?


举例2:


如果是5个元素,比如前序是[3,9,20,15,7],中序是[9,3,15,20,7]


经过第一次分割,我们把中序分成了左子树9,根节点3,右子树【15,20,7】


这时候,右子树该怎么分呢?跟节点是什么呢?又不知道了。


所以这时候要再联系到前序遍历,根据我们所知道的左子树节点,得出前序中右子树应该为【20,15,7】,所以右子树的根节点为20。


总之,就是前序和中序互相帮助,最终通过递归完成我们树的构建。


解法1


解法1就是依靠递归。


递归的过程就是找出每个父节点的左子树节点和右子树节点,一共三个值。


而最终的取值都是从前序列表中取值,其实就是取每个小树的父节点。


而中序遍历数组的作用就是找到 每次父节点在中序遍历数组中的位置。


得出如下算法。


/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int[] preorder;
    HashMap<Integer, Integer> dic = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for(int i = 0; i < inorder.length; i++)
            dic.put(inorder[i], i);
        return recur(0, 0, inorder.length - 1);
    }
    TreeNode recur(int root, int left, int right) {
        if(left > right) return null;        
        //根节点                  
        TreeNode node = new TreeNode(preorder[root]);   
        //分割点    
        int i = dic.get(preorder[root]);        
        node.left = recur(root + 1, left, i - 1); 
        node.right = recur(root + i - left + 1, i + 1, right); 
        return node;      
    }
}


其中每次取右子树的根节点需要注意:


  • 左子树的节点数为i-left。
  • 所以在前序遍历数组中,左子树的节点+根节点位置,就是左子树的最后一个节点位置,也就是root+i-left。
  • 最后得出右子树的根节点为:root + i - left + 1


而递归的结束条件就是,左右子树节点相遇,也就是left>right。


时间复杂度


O(n),n为树的节点数量。


空间复杂度


O(N),用到了HashMap。


解法2


还有一种办法叫做迭代方法,这个方法挺巧妙的,当时也是看了很久的官方解答才想明白的,哈哈。


它的主要思想是理解前序遍历和中序遍历的规则,然后一个个从前序遍历中取值,并放到合适的位置。


比如以下这个二叉树:


3
       / \
      9  20
     /  /  \
    8  15   7
   / \
  5  10
 /
4


前序遍历,可以发现是首先把最左边的节点列出来,也就是 3,9,8,5,4

而中序列表是反着来的,从最左边的下面开始,往上列,如果发现某个节点有右子节点,就开始往右边列。


比如 4,5,8。这时候发现8有右子节点,那么就开始数 10 ,然后继续最左边往上,9,3。


最后列一下完整的前序和中序:


preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7] inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]

总之,前序是从左边列开始,从上往下排。中序就是从左边列开始,从下往上排。


看看代码:


class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || preorder.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[0]);
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        stack.push(root);
        int inorderIndex = 0;
        for (int i = 1; i < preorder.length; i++) {
            int preorderVal = preorder[i];
            TreeNode node = stack.peek();
            if (node.val != inorder[inorderIndex]) {
                node.left = new TreeNode(preorderVal);
                stack.push(node.left);
            } else {
                while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
                    node = stack.pop();
                    inorderIndex++;
                }
                node.right = new TreeNode(preorderVal);
                stack.push(node.right);
            }
        }
        return root;
    }
}


if语句是为了找到左列最后一个节点,比如上述例子中的4。


while循环是当发现有右节点的时候,要找到右节点的父节点,然后添加进去。


大家可以手动试试,解法2确实不太好理解。


至少解法1的递归方法是要掌握的。


时间复杂度


O(n)


空间复杂度


O(n)


参考


https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/

目录
相关文章
|
6月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
303 14
|
7月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
188 10
|
7月前
|
存储 算法 数据可视化
【二叉树遍历入门:从中序遍历到层序与右视图】【LeetCode 热题100】94:二叉树的中序遍历、102:二叉树的层序遍历、199:二叉树的右视图(详细解析)(Go语言版)
本文详细解析了二叉树的三种经典遍历方式:中序遍历(94题)、层序遍历(102题)和右视图(199题)。通过递归与迭代实现中序遍历,深入理解深度优先搜索(DFS);借助队列完成层序遍历和右视图,掌握广度优先搜索(BFS)。文章对比DFS与BFS的思维方式,总结不同遍历的应用场景,为后续构造树结构奠定基础。
365 10
|
7月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
178 4
|
7月前
|
Go 索引 Perl
【LeetCode 热题100】【二叉树构造题精讲:前序 + 中序建树 & 有序数组构造 BST】(详细解析)(Go语言版)
本文详细解析了二叉树构造的两类经典问题:通过前序与中序遍历重建二叉树(LeetCode 105),以及将有序数组转化为平衡二叉搜索树(BST,LeetCode 108)。文章从核心思路、递归解法到实现细节逐一拆解,强调通过索引控制子树范围以优化性能,并对比两题的不同构造逻辑。最后总结通用构造套路,提供进阶思考方向,帮助彻底掌握二叉树构造类题目。
408 9
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
166 6
【LeetCode 31】104.二叉树的最大深度
【LeetCode 31】104.二叉树的最大深度
83 2
【LeetCode 29】226.反转二叉树
【LeetCode 29】226.反转二叉树
90 2
【LeetCode 28】102.二叉树的层序遍历
【LeetCode 28】102.二叉树的层序遍历
94 2
|
存储 算法
二叉树进阶-学会层序遍历助你一次刷完leetcode10道题
文章深入探讨了二叉树的层序遍历方法,并展示了如何通过队列实现层序遍历的算法逻辑,同时指出掌握层序遍历技巧可以帮助解决LeetCode上的多道相关题目。
二叉树进阶-学会层序遍历助你一次刷完leetcode10道题

热门文章

最新文章

下一篇
oss云网关配置