zoukankan      html  css  js  c++  java
  • 每日一道 LeetCode (24):将有序数组转换为二叉搜索树

    每天 3 分钟,走上算法的逆袭之路。

    前文合集

    每日一道 LeetCode 前文合集

    代码仓库

    GitHub: https://github.com/meteor1993/LeetCode

    Gitee: https://gitee.com/inwsy/LeetCode

    题目:将有序数组转换为二叉搜索树

    题目来源:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/

    将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

    本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

    示例:

    给定有序数组: [-10,-3,0,5,9],

    一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

          0
         / 
       -3   9
       /   /
     -10  5
    

    解题思路

    啥子玩意?还能通过一个数组逆推一个二叉树出来?这题出的还敢再 BT 点不。

    绝对是神仙思路,玩不转玩不转,直接投降翻答案。

    最开始,需要明确一件事儿,啥是二叉搜索树?

    二叉搜索的显著特点:

    • 对于树中的每个节点 X ,它的左子树中所有关键字值小于 X 的关键字值,而它的右子树中所有关键字值大于 X 的关键字值。

    简单来讲就是 左 < 根 < 右。

    根据这个特性就有了,如果一个二叉搜索树做中序遍历,那么会得到一个递增的序列。

    放到这道题里,就很明确了,这道题给出的数组序列实际上是一个中序遍历的二叉搜索树。

    官方的题解中,有几个问题比较好,我摘录过来(以下内容来源官方题解)。

    给定二叉搜索树的中序遍历,是否可以唯一地确定二叉搜索树?答案是否定的。如果没有要求二叉搜索树的高度平衡,则任何一个数字都可以作为二叉搜索树的根节点,因此可能的二叉搜索树有多个。

    如果增加一个限制条件,即要求二叉搜索树的高度平衡,是否可以唯一地确定二叉搜索树?答案仍然是否定的。

    直观地看,我们可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 11,可以使得树保持平衡。如果数组长度是奇数,则根节点的选择是唯一的,如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点,选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。

    到这一步,官方的题解已经把最艰难的部分解决了 「如何确定一个根节点」 。

    下一步需要做的是把其余的数字放在二叉树的左右子树里面。

    那么如何放呢?很简单,我们重复刚才的过程,比如左子树,先找到左子树的根节点(中间的那个数),然后递归这个过程。

    我只画了两次迭代,完整的画完这个图太大了, PPT 画不下。

    官方题解上给出来三种解法,一种是选择中间位置左边的数字作为根节点,另一种是选择中间位置右边的数字作为根节点,最后一种是把前两种结合起来。

    解题方案一:中序遍历,选择中间位置左边的数字作为根节点

    选择中间位置左边的数字作为根节点,则根节点的下标为 mid = (left + right) / 2,此处的除法为整数除法。

    public TreeNode sortedArrayToBST(int[] nums) {
        return helper(nums, 0, nums.length - 1);
    }
    
    // 中序遍历,选择中间位置左边的数字作为根节点
    public TreeNode helper(int[] nums, int left, int right) {
        if (left > right) return null;
    
        int mid = (left + right) / 2;
    
        TreeNode node = new TreeNode(nums[mid]);
        node.left = helper(nums, left, mid - 1);
        node.right = helper(nums, mid + 1, right);
        return node;
    }
    

    解题方案二:中序遍历,选择中间位置右边的数字作为根节点

    有了上面这个方案一,再写这个就已经很简单了,在这个方案中,根节点的计算公式为 mid = (left + right + 1) / 2

    // 中序遍历,选择中间位置右边的数字作为根节点
    public TreeNode helper_1(int[] nums, int left, int right) {
        if (left > right) return null;
    
        int mid = (left + right + 1) / 2;
    
        TreeNode node = new TreeNode(nums[mid]);
        node.left = helper(nums, left, mid - 1);
        node.right = helper(nums, mid + 1, right);
        return node;
    }
    

    实际上有变化就只有 mid 的取值,剩下啥都没变。

    解题方案三:中序遍历,选择任意一个中间位置数字作为根节点

    这个方案的 mid 取值就很有意思了,先不看后面,各位可以先思考下如何任意的取一个中间位置。

    也就是在 mid = (left + right) / 2mid = (left + right + 1) / 2 之间取一个值。

    官方的示例图我先放出来,别急着往下翻:

    结果是可以取一个随机数: mid = (left + right + new Random().nextInt(2)) / 2

    // 中序遍历,选择任意一个中间位置数字作为根节点
    public TreeNode helper_2(int[] nums, int left, int right) {
        if (left > right) return null;
    
        int mid = (left + right + new Random().nextInt(2)) / 2;
    
        TreeNode node = new TreeNode(nums[mid]);
        node.left = helper(nums, left, mid - 1);
        node.right = helper(nums, mid + 1, right);
        return node;
    }
    

    这个方案三的思路确实很清奇,有点意思。

    今天这道题需要通过一个数组逆推出来一个二叉搜索树,咳咳,第一次见确实不会做,就当背答案了。

    扫描二维码关注「极客挖掘机」公众号!
    作者:极客挖掘机
    定期发表作者的思考:技术、产品、运营、自我提升等。

    本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机
  • 相关阅读:
    PHP自动加载(__autoload和spl_autoload_register)
    抽象工厂模式
    工厂方法模式
    简单工厂模式
    组合
    二叉树的层次遍历 II
    umask命令
    二叉树的所有路径
    CDN缓存的理解
    Js中RegExp对象
  • 原文地址:https://www.cnblogs.com/babycomeon/p/13544689.html
Copyright © 2011-2022 走看看