leetcode
leetcode 901 ~ 950
前序遍历构造二叉搜索树

前序遍历构造二叉搜索树

难度:

标签:

题目描述

给定一个整数数组,它表示BST(即 二叉搜索树 )的 序遍历 ,构造树并返回其根。

保证 对于给定的测试用例,总是有可能找到具有给定需求的二叉搜索树。

二叉搜索树 是一棵二叉树,其中每个节点, Node.left 的任何后代的值 严格小于 Node.val , Node.right 的任何后代的值 严格大于 Node.val

二叉树的 前序遍历 首先显示节点的值,然后遍历Node.left,最后遍历Node.right

 

示例 1:

输入:preorder = [8,5,1,7,10,12]
输出:[8,5,10,1,7,null,12]

示例 2:

输入: preorder = [1,3]
输出: [1,null,3]

 

提示:

  • 1 <= preorder.length <= 100
  • 1 <= preorder[i] <= 10^8
  • preorder 中的值 互不相同

 

代码结果

运行时间: 32 ms, 内存: 14.9 MB


/*
 * 思路:
 * 1. 使用 Java Stream 构造二叉搜索树。
 * 2. 前序遍历数组的第一个元素是树的根节点。
 * 3. 使用流的分区功能将元素分为比根节点小的左子树和比根节点大的右子树。
 * 4. 递归地构造左子树和右子树。
 */

import java.util.*;
import java.util.stream.*;

public class Solution {
    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    public TreeNode bstFromPreorder(int[] preorder) {
        return constructBST(Arrays.stream(preorder).boxed().collect(Collectors.toList()));
    }

    private TreeNode constructBST(List<Integer> preorder) {
        if (preorder.isEmpty()) return null;
        TreeNode node = new TreeNode(preorder.get(0));
        List<Integer> leftSubtree = preorder.stream().skip(1).filter(x -> x < node.val).collect(Collectors.toList());
        List<Integer> rightSubtree = preorder.stream().skip(1).filter(x -> x > node.val).collect(Collectors.toList());
        node.left = constructBST(leftSubtree);
        node.right = constructBST(rightSubtree);
        return node;
    }
}

解释

方法:

首先,我们需要理解前序遍历的特点,即首个元素是树的根节点,其后是左子树的节点和右子树的节点。这个题解使用递归的方法,首先将根节点从前序遍历的首个元素建立,然后寻找左子树和右子树的分界点(即第一个大于根节点的元素),再对左右子树递归地进行同样的构建过程。这样,我们可以保证每次递归都正确地构建出左右子树。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
在构造二叉搜索树的时候,题解中提到使用递归方法处理,这种递归处理在极端情况下是如何表现的?例如当输入是一个已经排序的数组。
当输入的前序遍历结果是一个已经排序的数组时,例如 [1, 2, 3, 4, 5],每次递归调用都会将下一个元素作为树的右子节点,而没有左子节点。这将导致构造出的二叉搜索树退化成一个链状结构,即每个节点都只有右子节点。在这种情况下,递归的深度等于数组的长度,若数组长度很大,可能会导致递归栈溢出,从而影响算法的性能和稳定性。
🦆
题解中使用的辅助函数`helper`是否可以通过添加额外的参数或者使用其他数据结构来改善其效率?
可以通过使用额外的数据结构如栈来模拟递归过程,从而避免深度递归带来的栈溢出问题。此外,可以维护一个全局索引指针,而非每次递归传递新的索引范围,这样可以避免重复的边界计算。另一种改进方式是使用哈希表或平衡二叉树来快速定位右子树的开始位置,减少每次寻找分界点的时间复杂度。
🦆
在题解的递归构建过程中,如何保证每次递归调用都正确地构建出每个子树的根节点?
在每次递归调用中,通过选择数组中的当前元素作为根节点,并找到第一个大于根节点的元素的位置,从而确定左子树和右子树的范围。这种方法根据二叉搜索树的性质,即左子树的所有元素小于根节点,右子树的所有元素大于根节点,保证了每次递归都能正确地构建每个子树的根节点。
🦆
对于非常大的输入数组,题解中的方法可能会面临栈溢出的风险吗?如果是,有什么办法可以防止这种情况?
是的,对于非常大的输入数组,由于递归深度可能会非常高,尤其是在数组已经排序等极端情况下,题解中的递归方法可能会导致栈溢出。为了防止这种情况,可以考虑使用迭代法而非递归,或者在编程时使用尾递归优化。此外,可以将递归转换为使用显式栈的迭代方法,这样可以手动控制栈的使用,从而避免系统栈溢出的风险。

相关问题