从有序数组到平衡二叉树:二分查找的“降维打击”!
摘要:LeetCode 108. 将有序数组转换为二叉搜索树,你还在纠结怎么选根节点吗?其实这题本质就是二分查找的变种!本文带你用“分治法”一招制敌,轻松构建高度平衡的二叉搜索树!
前言
哈喽大家好,我是爱摸鱼的打工仔。
今天我们要攻克的是一道非常经典的二叉树题目——LeetCode 108. 将有序数组转换为二叉搜索树。
这题乍一看有点唬人,又是“二叉搜索树”,又是“高度平衡”。但如果你透过现象看本质,你会发现:这不就是二分查找换了个马甲吗?
很多小伙伴在做这题时,容易陷入“怎么插入节点”的误区,试图一个个往树里塞数据。千万别!那样不仅慢,还很难保证平衡。今天我就带大家换个思路,用**“分治法”**上帝视角,一次性把树建好!
核心知识点:二叉搜索树与平衡树
在动手写代码之前,我们先要搞清楚两个核心概念,这直接决定了我们的解题策略。
1. 什么是二叉搜索树(BST)?
- 定义:对于树中的每个节点,左子树的所有节点值都小于它,右子树的所有节点值都大于它。
- 关键性质:BST 的中序遍历结果是一个有序数组。
2. 什么是高度平衡?
- 定义:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。
- 通俗理解:树不能长成“瘸子”,要尽量长得匀称、矮胖,不能是瘦高个。
破题关键:
既然数组是有序的,而我们要构建一棵平衡的 BST。
为了让树平衡,我们必须让左右子树的节点数量尽可能接近。
怎么做?选数组的中间元素作为根节点!
解题思路:递归分治(上帝视角)
想象一下,你手里拿着一个有序数组 [-10, -3, 0, 5, 9]。
-
找根节点:为了让左右平衡,我们肯定选中间的那个数
0作为根。 -
分治:
0左边的[-10, -3]肯定都在左子树。0右边的[5, 9]肯定都在右子树。
-
递归:
- 对于左子树
[-10, -3],谁是根?选中间的-3。 - 对于右子树
[5, 9],谁是根?选中间的5(或者9,取决于取整方式)。
- 对于左子树
看明白了吗?这就是一个标准的二分查找过程。我们不需要一个个插入,而是直接通过下标来确定父子关系。
具体步骤:
-
定义递归函数:我们需要一个辅助函数,接收数组以及当前处理的左右边界
left和right。 -
终止条件:如果
left > right,说明区间空了,返回None。 -
单层逻辑:
- 找到中间下标
mid。 - 创建根节点
root,值为nums[mid]。 - 递归构建左子树(区间变为
left到mid-1)。 - 递归构建右子树(区间变为
mid+1到right)。
- 找到中间下标
-
返回:返回构建好的
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
复杂度分析
-
我们需要遍历数组中的每一个元素一次来创建节点,所以时间复杂度是线性的。 -
这里主要是递归调用栈的空间。因为我们要构建的是 平衡 二叉树,树的高度是 ,所以递归的最大深度也是 。
总结
这道题虽然被标记为“简单”,但它考察了两个非常重要的概念:
- 二叉搜索树的性质:中序遍历是有序的。
- 分治算法的思想:通过不断选取中点,将大问题拆解为左右两个小问题。
下次遇到“有序数组”转“树”或者“链表”的题目,记得第一时间想到**“找中点,递归构建”**这个套路哦!
我是爱摸鱼的打工仔,我们下期再见!