1778 words
9 minutes
Leetcode Hot 100 将有序数组转换为二叉搜索树

Leetcode Hot 100 继续在树上施工,这一题看着像“建树题”,实际上考的是一手很标准的分治递归将有序数组转换为二叉搜索树

题目给你一个升序数组,不是让你随便拼一棵树, 而是要你拼出一棵:

  • 二叉搜索树
  • 高度平衡

也就是说,这题不是“能建出来就行”, 而是“既要合法,还得长得匀称”。

题目链接#

LeetCode 108. 将有序数组转换为二叉搜索树

题目描述#

给你一个整数数组 nums,其中元素已经按升序排列,请你将其转换为一棵高度平衡的二叉搜索树。

这里的“高度平衡”指的是:

每个节点的左右两个子树高度差的绝对值不超过 1

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

解题思路:每次取中点作为根节点#

这题最核心的一步,就是:

每次取当前有序数组的中间元素,作为这一棵子树的根节点。

为什么中点这么关键?

因为数组已经有序:

  • 中点左边的元素都比它小
  • 中点右边的元素都比它大

这正好满足二叉搜索树的性质:

  • 左子树节点值 < 根节点值
  • 右子树节点值 > 根节点值

同时,中点还能把数组尽量均匀地分成两半, 这样左右子树规模接近,整棵树就更容易保持平衡。

所以这题的套路非常清晰:

  1. 取中间元素建根节点
  2. 左半部分递归构造左子树
  3. 右半部分递归构造右子树
  4. 数组为空时返回 None

整个过程就是一个标准分治。

代码实现#

class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: Optional[TreeNode]
"""
def f(num_lists):
if not num_lists:
return None
n = len(num_lists)
idx = n // 2
node = TreeNode(val=num_lists[idx])
node.left = f(num_lists[:idx])
node.right = f(num_lists[idx + 1:])
return node
if len(nums) == 0:
return None
return f(nums)

代码解析#

1. 递归函数 f(num_lists) 是干什么的#

这个函数表示:

把当前这段有序数组,转换成一棵高度平衡的二叉搜索树,并返回根节点。

也就是说,函数的输入是一段有序数组, 输出是一棵对应的 BST 子树。

2. 边界情况:数组为空时返回 None#

if not num_lists:
return None

这是递归里最重要的刹车片。

如果当前数组已经空了, 说明这里已经没有节点可以建, 那就返回空节点。

你提到的“要注意边界情况”,说得很对。 这题的递归不难,真正不能漏的恰恰就是这个空数组判断。

3. 为什么取 n // 2 作为根节点#

n = len(num_lists)
idx = n // 2
node = TreeNode(val=num_lists[idx])

取中间位置的元素作为根节点,有两个直接好处:

第一,满足 BST 性质#

因为数组有序:

  • idx 左边的都更小
  • idx 右边的都更大

所以左边天然适合当左子树, 右边天然适合当右子树。

第二,尽量保持平衡#

因为中间元素把数组分成两半, 左右子树节点数量差距最小, 更容易形成高度平衡的结构。

如果你不取中点, 比如老挑最左边或者最右边, 那树就很容易一路歪下去, 最后长成一根“树形天线”。

4. 递归构造左右子树#

node.left = f(num_lists[:idx])
node.right = f(num_lists[idx + 1:])

这一步就是分治的核心:

  • 左半段递归生成左子树
  • 右半段递归生成右子树

而且切出来的左右数组依然是有序的, 所以递归条件能够继续成立。

换句话说:

每一层都在做同一件事:拿中点做根,再把左右两段继续递归。

这就是递归之所以顺手的原因。

示例理解#

比如输入:

nums = [-10, -3, 0, 5, 9]

第一次取中点:

0

于是根节点就是:

0

接下来分成两段:

  • 左边:[-10, -3]
  • 右边:[5, 9]

继续递归:

  • 左边会选 -3 做根
  • 右边会选 9 做根

然后再往下拆。

整个过程很像:

不断从中间劈开数组,再把每一块的中点拎出来当根节点。

最后就能得到一棵平衡 BST。

这份写法的优缺点#

你现在这版代码的优点很明显:

  • 写法直观
  • 逻辑清楚
  • 很适合讲题解

但它也有一个小代价:

num_lists[:idx]
num_lists[idx + 1:]

这里每次递归都会创建新的子数组切片。

所以它虽然好懂, 但不是最省空间、最省时间的写法。

复杂度分析(当前切片写法)#

时间复杂度#

由于递归过程中不断切片,会有额外拷贝开销, 整体时间复杂度通常可以看作:

O(n log n)

空间复杂度#

除了递归栈之外, 切片本身也会额外占空间, 所以空间复杂度也不算低。

这个版本的优势不在极致优化, 而在于:

好理解、好实现、好写题解。

如果是刷题或者写博客,这版完全够用。

如果想优化,可以怎么做#

更优的办法是:

  • 不传子数组
  • 改成传左右边界 leftright

这样就不会反复创建新数组, 效率会更好。

不过对于当前这题, 先把“中点分治建树”的核心讲明白更重要。 优化版可以当进阶补充, 不一定非得第一时间上。

易错点总结#

1. 忘记处理空数组#

if not num_lists:
return None

这句必须有, 不然递归走到空列表时就会直接翻车。

2. 根节点不是随便选的#

这题不是“从数组里挑一个数当根”就完事。

要满足“高度平衡”, 就必须尽量从中间挑。

3. 只顾 BST,忘了平衡#

很多同学会觉得:

数组有序,那我怎么建都能比较顺吧?

不对。

“顺”不等于“平衡”。

题目要求的是:

  • 是二叉搜索树
  • 还得高度平衡

所以中点选择才是灵魂。

4. 以为 if len(nums) == 0 那句是必须的#

if len(nums) == 0:
return None

这句当然没问题,写着也清楚。

但严格来说,有了递归里的:

if not num_lists:
return None

外层这句其实可以省掉,直接:

return f(nums)

也能正确运行。

不过保留也没毛病,算是提前把空输入拦一下。

小结#

这题的核心其实就一句:

每次取有序数组的中点作为根节点,再递归构造左右子树。

这样做可以同时满足两件事:

  • 保证二叉搜索树性质
  • 尽量保持整棵树高度平衡

所以它本质上是一道非常典型的分治递归建树题。 数组一劈两半,中点上位称王;左边去建左树,右边去建右树,整套动作行云流水。 只要边界别漏,这题基本就是递归稳稳拿下。🦐

Leetcode Hot 100 将有序数组转换为二叉搜索树
https://ssonnyboy.github.io/yoroziya/posts/leetcode-hot-100-convert-sorted-array-to-binary-search-tree/
Author
biabuluo
Published at
2026-04-04
License
CC BY-NC-SA 4.0