LeetCode 108. 将有序数组转换为二叉搜索树(递归构建详解 + Java/Python 实现)
题目链接
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
题目描述
给你一个整数数组 nums,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例
示例 1
text
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案。

示例 2
text
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。

提示
1 <= nums.length <= 10^4-10^4 <= nums[i] <= 10^4nums按 严格递增 顺序排列
解题思路
这道题要求将有序数组转换为 平衡二叉搜索树(BST)。平衡意味着左右子树的高度差不超过 1。
核心思想:递归 + 二分
由于数组是升序排列的,我们可以利用二分查找的思想:
- 选择中间元素作为根节点:这样左半部分的元素都小于根节点,右半部分的元素都大于根节点,自然满足 BST 的性质。
- 递归构建左右子树 :
- 左子树由数组左半部分构建。
- 右子树由数组右半部分构建。
- 递归终止条件 :当
left > right时,说明没有元素需要构建,返回None(或null)。
为什么选择中间元素作为根节点可以保证平衡?
因为每次都将数组均匀地分成两部分,左子树和右子树的节点数最多相差 1,所以整棵树是高度平衡的。
举例说明
以 nums = [-10,-3,0,5,9] 为例:
- 数组长度为 5,中间索引为 2,
nums[2] = 0作为根节点。 - 左半部分
[-10,-3],中间索引为 0,nums[0] = -10作为左子树的根节点。 - 左子树的右半部分
[-3],nums[1] = -3作为-10的右子节点。 - 右半部分
[5,9],中间索引为 3,nums[3] = 5作为右子树的根节点。 - 右子树的右半部分
[9],nums[4] = 9作为5的右子节点。
最终得到的树结构:
text
0
/ \
-10 5
\ \
-3 9
这是一棵平衡二叉搜索树。
代码实现
Java 版本
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return buildTree(nums, 0, nums.length - 1);
}
public TreeNode buildTree(int[] nums, int left, int right){
if (left > right){
return null;
}
int mid = left + (right - left) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = buildTree(nums, left, mid - 1);
root.right = buildTree(nums, mid + 1, right);
return root;
}
}
Python 版本
python
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: Optional[TreeNode]
"""
return self.buildTree(nums, 0, len(nums) - 1)
def buildTree(self, nums, left, right):
if left > right:
return None
mid = left + (right - left) / 2
root = TreeNode(nums[mid])
root.left = self.buildTree(nums, left, mid - 1)
root.right = self.buildTree(nums, mid + 1, right)
return root
注意 :Python 3 中
/会产生浮点数,应使用//整除:mid = left + (right - left) // 2
代码说明
- 主函数
sortedArrayToBST:调用辅助函数buildTree,传入数组和初始的左右边界。 - 辅助函数
buildTree:- 参数 :
nums数组,left左边界,right右边界。 - 终止条件 :
if (left > right) return null;,当左边界大于右边界时,说明没有元素需要构建。 - 计算中间位置 :
int mid = left + (right - left) / 2;,使用这种方式避免整数溢出。 - 创建根节点 :
TreeNode root = new TreeNode(nums[mid]);。 - 递归构建左子树 :
root.left = buildTree(nums, left, mid - 1);,范围为[left, mid-1]。 - 递归构建右子树 :
root.right = buildTree(nums, mid + 1, right);,范围为[mid+1, right]。 - 返回根节点 :
return root;。
- 参数 :
复杂度分析
- 时间复杂度 :
O(n),其中n是数组的长度。每个元素只被访问一次,用于创建节点。 - 空间复杂度 :
O(log n),递归调用的栈深度。由于树是平衡的,递归深度为树的高度,即O(log n)。
关键点解析
1. 为什么使用 left + (right - left) / 2 而不是 (left + right) / 2?
两者在数学上是等价的,但 left + (right - left) / 2 可以避免 left + right 可能导致的整数溢出问题。虽然在这道题的数据范围内不会溢出,但这是编程中的好习惯。
2. 为什么选择中间元素作为根节点?
选择中间元素作为根节点可以保证左右子树的节点数尽量相等,从而保证树的高度平衡。如果选择其他位置的元素,可能导致树不平衡。
3. 递归终止条件为什么是 left > right?
当 left == right 时,说明还有一个元素需要构建节点。当 left > right 时,说明没有元素了,应该返回 null。
4. 多个正确答案的问题
题目说 [0,-3,9,-10,null,5] 和 [0,-10,5,null,-3,null,9] 都是正确答案。这是因为当数组长度为偶数时,中间位置有两个选择(左中位数或右中位数),选择不同的中间位置会得到不同的树结构,但都是平衡 BST。
例如 nums = [1,3]:
- 选择左中位数(索引 0):根节点为 1,右子树为 3,得到
[1,null,3]。 - 选择右中位数(索引 1):根节点为 3,左子树为 1,得到
[3,1]。
两种都是正确答案。
递归过程详解
以 nums = [-10,-3,0,5,9] 为例,详细展示递归过程:
text
buildTree(nums, 0, 4)
├── mid = 2, root = 0
├── buildTree(nums, 0, 1) # 左子树
│ ├── mid = 0, root = -10
│ ├── buildTree(nums, 0, -1) # 返回 null
│ └── buildTree(nums, 1, 1) # 右子树
│ ├── mid = 1, root = -3
│ ├── buildTree(nums, 1, 0) # 返回 null
│ └── buildTree(nums, 2, 1) # 返回 null
└── buildTree(nums, 3, 4) # 右子树
├── mid = 3, root = 5
├── buildTree(nums, 3, 2) # 返回 null
└── buildTree(nums, 4, 4) # 右子树
├── mid = 4, root = 9
├── buildTree(nums, 4, 3) # 返回 null
└── buildTree(nums, 5, 4) # 返回 null
最终构建的树:
text
0
/ \
-10 5
\ \
-3 9
总结
这道题是递归构建二叉树的经典题目,核心要点:
- 选择中间元素作为根节点:保证左右子树平衡。
- 递归构建左右子树 :左子树范围
[left, mid-1],右子树范围[mid+1, right]。 - 终止条件 :
left > right时返回null。
关键点回顾:
- 使用二分思想,选择中间元素作为根节点
- 递归构建左右子树
- 时间复杂度
O(n),空间复杂度O(log n) - 数组长度为偶数时,选择不同的中间位置会得到不同的正确答案
这个递归模板也适用于其他树构建问题,建议熟练掌握。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!