【力扣-108.将有序数组转换为二叉搜索树】Python笔记

0 阅读4分钟

从有序数组到平衡二叉树:二分查找的“降维打击”!

摘要:LeetCode 108. 将有序数组转换为二叉搜索树,你还在纠结怎么选根节点吗?其实这题本质就是二分查找的变种!本文带你用“分治法”一招制敌,轻松构建高度平衡的二叉搜索树!


前言

哈喽大家好,我是爱摸鱼的打工仔

今天我们要攻克的是一道非常经典的二叉树题目——LeetCode 108. 将有序数组转换为二叉搜索树

这题乍一看有点唬人,又是“二叉搜索树”,又是“高度平衡”。但如果你透过现象看本质,你会发现:这不就是二分查找换了个马甲吗?

很多小伙伴在做这题时,容易陷入“怎么插入节点”的误区,试图一个个往树里塞数据。千万别!那样不仅慢,还很难保证平衡。今天我就带大家换个思路,用**“分治法”**上帝视角,一次性把树建好!


核心知识点:二叉搜索树与平衡树

在动手写代码之前,我们先要搞清楚两个核心概念,这直接决定了我们的解题策略。

1. 什么是二叉搜索树(BST)?

  • 定义:对于树中的每个节点,左子树的所有节点值都小于它,右子树的所有节点值都大于它。
  • 关键性质:BST 的中序遍历结果是一个有序数组

2. 什么是高度平衡?

  • 定义:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。
  • 通俗理解:树不能长成“瘸子”,要尽量长得匀称、矮胖,不能是瘦高个。

破题关键
既然数组是有序的,而我们要构建一棵平衡的 BST。
为了让树平衡,我们必须让左右子树的节点数量尽可能接近。
怎么做?选数组的中间元素作为根节点!


解题思路:递归分治(上帝视角)

想象一下,你手里拿着一个有序数组 [-10, -3, 0, 5, 9]

  1. 找根节点:为了让左右平衡,我们肯定选中间的那个数 0 作为根。

  2. 分治

    • 0 左边的 [-10, -3] 肯定都在左子树。
    • 0 右边的 [5, 9] 肯定都在右子树。
  3. 递归

    • 对于左子树 [-10, -3],谁是根?选中间的 -3
    • 对于右子树 [5, 9],谁是根?选中间的 5(或者9,取决于取整方式)。

看明白了吗?这就是一个标准的二分查找过程。我们不需要一个个插入,而是直接通过下标来确定父子关系。

具体步骤:

  1. 定义递归函数:我们需要一个辅助函数,接收数组以及当前处理的左右边界 leftright

  2. 终止条件:如果 left > right,说明区间空了,返回 None

  3. 单层逻辑

    • 找到中间下标 mid
    • 创建根节点 root,值为 nums[mid]
    • 递归构建左子树(区间变为 leftmid-1)。
    • 递归构建右子树(区间变为 mid+1right)。
  4. 返回:返回构建好的 root


代码实现(Python3)

代码逻辑非常清晰,我加上了详细的注释,方便你理解每一步的操作:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        # 主函数入口,调用递归辅助函数
        # 初始时,处理整个数组,范围是从下标 0 到 len(nums)-1
        return self.build(nums, 0, len(nums) - 1)
    
    # 递归辅助函数:在 nums[left...right] 区间内构造平衡 BST
    def build(self, nums: List[int], left: int, right: int) -> Optional[TreeNode]:
        # 1. 递归终止条件
        # 如果左边界超过了右边界,说明当前区间没有元素了,返回空节点
        if left > right:
            return None
        
        # 2. 核心逻辑:找中点
        # 使用 (left + right) // 2 找到中间位置
        # 选中间元素作为根节点,能保证左右子树节点数量差异不超过1,从而保证平衡
        mid = (left + right) // 2
        
        # 3. 创建当前层的根节点
        root = TreeNode(nums[mid])
        
        # 4. 递归构建左子树
        # 左子树的元素都在 mid 的左边,所以区间是 [left, mid - 1]
        root.left = self.build(nums, left, mid - 1)
        
        # 5. 递归构建右子树
        # 右子树的元素都在 mid 的右边,所以区间是 [mid + 1, right]
        root.right = self.build(nums, mid + 1, right)
        
        # 6. 返回当前构建好的子树根节点
        return root

复杂度分析


  • 我们需要遍历数组中的每一个元素一次来创建节点,所以时间复杂度是线性的。

  • 这里主要是递归调用栈的空间。因为我们要构建的是 平衡 二叉树,树的高度是 logN\log N,所以递归的最大深度也是 logN\log N

总结

这道题虽然被标记为“简单”,但它考察了两个非常重要的概念:

  1. 二叉搜索树的性质:中序遍历是有序的。
  2. 分治算法的思想:通过不断选取中点,将大问题拆解为左右两个小问题。

下次遇到“有序数组”转“树”或者“链表”的题目,记得第一时间想到**“找中点,递归构建”**这个套路哦!

我是爱摸鱼的打工仔,我们下期再见!