一、题目描述
给你一个整数数组 nums,其中元素已经按照 升序排列 ,请你将它转换为一棵 高度平衡 的二叉搜索树。
高度平衡二叉树:每个节点的左右两个子树的高度差不超过 1。
示例:
输入:
nums = [-10,-3,0,5,9]
输出:
0
/ \
-3 9
/ /
-10 5
对应数组:
[-10,-3,0,5,9]
构造成:
0
/ \
-3 9
/ /
-10 5
二、为什么这道题值得学习?
这道题是二叉树构造类问题的经典入门题,也是面试中经常考察的递归应用。
它考察的核心:
1. 二叉搜索树性质
二叉搜索树(BST)满足:
- 左子树所有节点 < 根节点
- 右子树所有节点 > 根节点
因为数组本身已经有序,所以我们可以直接利用中间位置作为根节点。
2. 平衡思想
如果每次都选择数组中间元素作为根节点:
- 左半部分构造左子树
- 右半部分构造右子树
那么左右子树规模接近,可以保证树的高度平衡。
3. 递归构造思维
这道题本质是:
用一个数组区间,构造一棵对应的二叉树。
核心就是:
确定根节点
↓
递归构造左子树
↓
递归构造右子树
三、核心思想:选择中间节点作为根
因为数组是升序:
例如:
[-10,-3,0,5,9]
中间元素:
0
作为根节点。
那么:
左边:
[-10,-3]
继续构造左子树。
右边:
[5,9]
继续构造右子树。
过程:
0
/ \
[-10,-3] [5,9]
0
/ \
-3 9
/ /
-10 5
不断重复这个过程,直到数组为空。
四、递归三部曲
做递归题,可以按照三个步骤分析:
1. 确定递归函数
我们定义:
buildTree(nums, left, right)
含义:
使用 numsleft...right 这个区间构造一棵二叉搜索树,并返回根节点。
2. 确定递归终止条件
当:
left > right
说明数组区间为空。
没有节点:
return null;
3. 确定单层递归逻辑
步骤:
第一步:找到中间位置
int mid = left + (right - left) / 2;
避免:
(left + right)
可能产生整数溢出。
第二步:创建根节点
TreeNode root = new TreeNode(nums[mid]);
第三步:递归构造左右子树
左区间:
[left, mid-1]
右区间:
[mid+1, right]
代码:
root.left = buildTree(nums,left,mid-1);
root.right = buildTree(nums,mid+1,right);
五、解法:递归法(面试首选 ✅)
Java代码
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return buildTree(nums,0,nums.length-1);
}
private 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;
}
}
六、过程图解
数组:
[-10,-3,0,5,9]
第一次:
mid=2
0
左区间:
[-10,-3]
右区间:
[5,9]
第二次:
左:
[-10,-3]
mid=0
-10
\
-3
右:
[5,9]
mid=3
5
\
9
最终:
0
/ \
-10 5
\ \
-3 9
七、复杂度分析
时间复杂度:O(N)
原因:
数组中的每个元素都会被创建成一个节点。
所以:
访问 N 个元素
时间复杂度:
O(N)
空间复杂度:O(logN)
递归调用栈的深度取决于树的高度。
因为构造的是平衡二叉树:
高度约为:
logN
所以:
O(logN)
八、常见错误与避坑指南
❌ 错误一:直接选择第一个元素作为根节点
例如:
[-10,-3,0,5,9]
如果:
root=-10
会形成:
-10
\
-3
\
0
\
5
\
9
退化成链表。
失去平衡。
❌ 错误二:mid计算方式错误
错误:
int mid=(left+right)/2;
虽然一般情况下没问题,但存在整数溢出的风险。
推荐:
int mid=left+(right-left)/2;
❌ 错误三:递归区间写错
左子树:
正确:
left -> mid-1
错误:
left -> mid
会导致重复使用中间节点。
右子树:
正确:
mid+1 -> right
九、面试高频追问
1️⃣ 为什么一定选择中间节点?
因为数组已经有序。
选择中间节点:
- 左右元素数量接近
- 左右子树高度接近
- 可以保证平衡
2️⃣ 可以不用递归吗?
可以。
可以使用队列模拟递归:
保存:
数组区间 + 对应树节点
但是代码复杂度更高。
面试中递归更加自然。
3️⃣ 如果数组不是升序怎么办?
需要先排序:
排序 O(NlogN)
+
构造 O(N)
整体:
O(NlogN)