二叉树题解——将有序数组转换为二叉搜索树【LeetCode】优化解法
108. 将有序数组转换为二叉搜索树
方法一、索引递归版
一、算法逻辑(逐步通顺讲解每一步思路)
该题要求将一个 升序整数数组 nums
转换为一棵高度平衡的二叉搜索树(BST),并返回其根节点。
✅ 1️⃣ 递归构建树的入口函数
调用 dfs(0, len(nums))
处理整个数组范围,即从头到尾递归构造树结构。
✅ 2️⃣ 设定递归边界
dfs(left, right)
表示要将 nums[left:right]
这段数组构造成一棵子树。
当 left == right
时,子数组为空,没有节点可构建,直接返回 None
。
✅ 3️⃣ 选择中点作为当前子树的根
使用 mid = (left + right) // 2
取当前范围的中间元素作为根节点的值 nums[mid]
。这样可以保持树尽可能平衡(左右子树大小接近)。
✅ 4️⃣ 递归构建左右子树
-
左子树递归调用
dfs(left, mid)
:表示中点左边的元素形成左子树; -
右子树递归调用
dfs(mid + 1, right)
:表示中点右边的元素形成右子树。
✅ 5️⃣ 构造当前节点并拼接子树
每一层递归中,创建一个新的 TreeNode
节点,将左右递归结果接入后返回,即完成当前子树构建。
这样从上而下、从中间向两边地递归构造,最终形成了一棵平衡的二叉搜索树。
二、核心点总结
该算法的核心是:
分治 + 递归思想,选中点为根节点,递归构建左右子树,保持树的高度平衡。
✅ 使用数组中点作为当前子树的根节点,有效保证平衡性;
✅ 不断分割区间构建左右子树,天然匹配树的结构;
✅ 用索引代替切片,避免多余内存开销;
✅ 是将“数组 → 树结构”的典型构造模板。
class Solution:def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:def dfs(left:int, right:int) -> Optional[TreeNode]:if left == right:return Nonemid = (left + right)//2return TreeNode(nums[mid], dfs(left, mid), dfs(mid+1, right))return dfs(0, len(nums))
三、时间复杂度分析
每个元素都被访问一次,并且被构造成一个 TreeNode
。
-
每次递归构造一个节点,总共构造
n
个节点; -
虽然递归树有深度
log n
,但每一层的处理是分摊进行的,不重复处理元素。
时间复杂度:O(n)
四、空间复杂度分析
主要空间开销来源是 递归调用栈:
-
因为递归是平衡地分裂,最多递归
log n
层(等价于树的高度); -
不使用额外数组或辅助结构,空间占用主要来自递归本身。
空间复杂度:O(log n)
✅ 总结一句话
本算法通过分治递归+中点选根策略,在 O(n) 时间、O(log n) 空间内将有序数组构造成一棵平衡的 BST,是数组转树结构的经典构造技巧。
方法二、切片版
一、算法逻辑(逐步通顺讲解每一步思路)
该题要求将一个 升序排列的数组 nums
构造成一棵高度平衡的二叉搜索树(BST)。
✅ 1️⃣ 递归出口判断
首先判断数组是否为空,if not nums:
。为空时返回 None
,表示当前子树为空,是递归的结束条件。
✅ 2️⃣ 选择中间元素作为当前子树的根节点
使用 mid = len(nums) // 2
,取当前数组的中间索引;
将 nums[mid]
作为当前子树的根节点的值,确保左右子树元素数量尽可能接近。
✅ 3️⃣ 构建左子树和右子树
-
左子树通过递归调用
self.sortedArrayToBST(nums[:mid])
构造,传入左半部分; -
右子树通过
self.sortedArrayToBST(nums[mid+1:])
构造,传入右半部分;
这两个递归调用分别对应中点两侧的子数组,继续重复当前的构造逻辑。
✅ 4️⃣ 返回当前根节点
使用 TreeNode(nums[mid], left, right)
创建当前节点,并接上递归得到的左右子树,形成当前子树的结构并返回。
这种方式从上到下递归、从中间向两端扩展,最终构建出一棵平衡的 BST。
二、核心点总结
该算法的核心是:
利用递归 + 数组切片 + 中点选根策略,构造高度平衡的 BST。
✅ 选中点作为根节点,有助于保证左右子树平衡;
✅ 使用递归天然符合“树结构的自相似”构造方式;
✅ 切片语法使代码简洁易读,但也带来了额外空间开销;
✅ 是经典的“分治 + 中点做根 + 递归左右”的树构造模式。
class Solution:def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:if not nums:return Nonemid = len(nums) // 2left = self.sortedArrayToBST(nums[:mid])right = self.sortedArrayToBST(nums[mid + 1:])return TreeNode(nums[mid], left, right)
三、时间复杂度分析
设数组长度为 n
,递归深度为 log n
。
但这段代码中使用了 nums[:mid]
和 nums[mid+1:]
切片操作,而 Python 中切片操作的时间复杂度是 O(k),需要复制新数组。
-
每层会生成两个切片,平均长度约为
n/2
,n/4
, ..., 总和为O(n log n)
-
虽然节点数总共是
n
,但切片操作在每层都创建了新的数组副本。
因此该算法的时间复杂度为:
O(n log n)(构建树的递归是 O(n),切片复制引入了额外的 log n 层开销)
四、空间复杂度分析
空间开销来源于两个方面:
-
递归栈深度:最多递归
log n
层(因为是平衡二叉树); -
数组切片副本:每次递归都会创建新的子数组,总体空间开销累积为
O(n)
。
因此该算法的空间复杂度为:
O(n)(包含递归栈
O(log n)
和切片数组副本O(n)
)
✅ 总结一句话
本算法通过递归和数组切片构建平衡 BST,代码简洁直观,但切片带来的复制开销使得时间复杂度上升为 O(n log n),空间复杂度也为 O(n)。若想进一步优化效率,建议使用索引边界递归的方式。
对比:
指标 | 切片版 | 索引版(推荐) |
---|---|---|
✅ 代码简洁度 | 较高(语义直观) | 稍繁琐(需传索引) |
✅ 时间复杂度 | O(n log n)(切片复制) | O(n) |
✅ 空间复杂度 | O(n)(包含切片副本) | O(log n)(递归栈) |
✅ 实际运行速度 | 明显较慢 | 明显更快 |
✅ 是否原地操作 | 否(创建新数组) | 是(基于原数组) |
切片版易懂但低效,索引版稍繁但高效。若追求 LeetCode 提交性能或面试表现,应优先选择“索引边界递归法”。