每日一道 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;
}

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

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

posted @ 2020-08-22 10:41  极客挖掘机  阅读(387)  评论(0编辑  收藏  举报