一、题目描述
给你一个整数数组 nums,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
平衡二叉搜索树:左右子树高度差不超过 1 的二叉搜索树。
示例
| 示例 | 输入 | 输出 |
|---|---|---|
| 示例1 | nums = [-10,-3,0,5,9] |
[0,-3,9,-10,null,5] |
| 示例2 | nums = [1,3] |
[3,1] |
示例1的转换过程:
数组:[-10, -3, 0, 5, 9]
0 ← 中间元素作为根
/ \
-3 9 ← 左右两部分递归构建
/ \ / \
-10 # # 5 ← # 表示 null
可选的平衡BST(根为中间元素):
0 0
/ \ / \
-3 9 -10 5
/ \ \ /
-10 5 -3 9
提示
1 <= nums.length <= 10^4-10^4 <= nums[i] <= 10^4nums按 严格递增 顺序排列
二、解题思路总览
| 方法 | 核心思想 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 递归 | 选取中间元素为根,构建平衡树 | O(n) | O(log n) |
核心思想:
- 数组已排序,中间元素恰好是BST的根节点
- 左半部分构建左子树,右半部分构建右子树
- 递归处理,自然平衡
为什么选中间元素?
- 根是中间元素,左右元素数量相等或差1
- 左右子树高度差 ≤ 1,保证平衡
- BST的中序遍历正好是原数组
三、完整代码
cpp
class Solution {
public:
TreeNode* traversal(vector<int>& nums, int begin, int end) {
if (begin > end) return nullptr; // 1. 递归终止:空区间
int mid = (end + begin) / 2; // 2. 取中间元素作为根
TreeNode* root = new TreeNode(nums[mid]); // 3. 创建根节点
root->left = traversal(nums, begin, mid - 1); // 4. 递归构建左子树
root->right = traversal(nums, mid + 1, end); // 5. 递归构建右子树
return root; // 6. 返回根节点
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return traversal(nums, 0, nums.size() - 1); // 7. 入口调用
}
};
四、算法流程图(ASCII)
示例1的转换过程
数组:[-10, -3, 0, 5, 9]
0 ← 中间元素作为根
/ \
-3 9 ← 左右两部分递归构建
/ \ / \
-10 # # 5 ← # 表示 null
递归展开过程:
traversal([-10,-3,0,5,9], 0, 4)
│
├── mid = (0+4)/2 = 2 → nums[2] = 0
│
├── traversal(..., 0, 1) ← 左半部分
│ │
│ ├── mid = (0+1)/2 = 0 → nums[0] = -10
│ │
│ ├── left = traversal(..., 0, -1) → nullptr
│ ├── right = traversal(..., 1, 1) → nullptr
│ └── return TreeNode(-10)
│
├── traversal(..., 3, 4) ← 右半部分
│ │
│ ├── mid = (3+4)/2 = 3 → nums[3] = 5
│ │
│ ├── left = traversal(..., 3, 2) → nullptr
│ ├── right = traversal(..., 4, 4) → nullptr
│ └── return TreeNode(5)
│
└── return TreeNode(0)
树结构对比
严格平衡的BST:
0 高度 = 3
/ \
-3 9 高度 = 2 高度 = 2
/ \ 高度 = 1 高度 = 1
-10 5 高度 = 1 高度 = 1
\
(null)
每个节点的左右子树高度差 ≤ 1
五、逐行解析
cpp
class Solution {
public:
// ─────────────────────────────────────────
// 递归函数:在 nums[begin...end] 区间构建BST
// 返回构建好的树的根节点
// ─────────────────────────────────────────
TreeNode* traversal(vector<int>& nums, int begin, int end) {
// ─────────────────────────────────────────
// 第1步:递归终止条件
// begin > end 表示空区间,返回 nullptr
// ─────────────────────────────────────────
if (begin > end) return nullptr;
// ─────────────────────────────────────────
// 第2步:取中间元素作为根节点
// (begin + end) / 2 取下取整
// 中间元素保证左右子树平衡
// ─────────────────────────────────────────
int mid = (end + begin) / 2;
// ─────────────────────────────────────────
// 第3步:创建根节点
// 根节点值为中间元素
// ─────────────────────────────────────────
TreeNode* root = new TreeNode(nums[mid]);
// ─────────────────────────────────────────
// 第4步:递归构建左子树
// 左半部分:[begin, mid-1]
// ─────────────────────────────────────────
root->left = traversal(nums, begin, mid - 1);
// ─────────────────────────────────────────
// 第5步:递归构建右子树
// 右半部分:[mid+1, end]
// ─────────────────────────────────────────
root->right = traversal(nums, mid + 1, end);
// ─────────────────────────────────────────
// 第6步:返回根节点
// 供父节点连接
// ─────────────────────────────────────────
return root;
}
// ─────────────────────────────────────────
// 入口函数
// ─────────────────────────────────────────
TreeNode* sortedArrayToBST(vector<int>& nums) {
// 空数组直接返回 nullptr
if (nums.empty()) return nullptr;
// 从整个数组范围开始构建
return traversal(nums, 0, nums.size() - 1);
}
};
六、复杂度分析
时间复杂度
| 分析 | 复杂度 |
|---|---|
| 每个元素访问一次 | O(n) |
推导:
- n 个元素,每个元素作为根访问一次
- 构建节点是 O(1)
- 总共 n 个节点,所以 O(n)
空间复杂度
| 分析 | 复杂度 |
|---|---|
| 递归调用栈深度 = 树高 = log n | O(log n) |
推导:
- 每次递归区间减半,所以递归深度是 log n(以2为底)
- 不计输出空间(新建的树节点),栈空间是 O(log n)
七、面试追问 FAQ
| 问题 | 回答 |
|---|---|
| 为什么中间元素作为根能保证平衡? | 数组已排序,中间元素左右元素数量相等或差1,所以左右子树节点数相等或差1 |
| 可以选其他位置作为根吗? | 可以,但必须是中间附近的元素,否则树会不平衡 |
| 如果数组有偶数个元素,选哪个中间? | 选中间两个中的任意一个都可以,结果都是平衡BST |
| BST的中序遍历有什么特点? | BST中序遍历结果是升序数组,正好是原数组 |
| 为什么不直接用二分查找的思路? | 本题就是二分构建,BST的根就是二分的中点 |
八、相关题目
| 题目 | 难度 | 关键点 |
|---|---|---|
| 108. 将有序数组转换为二叉搜索树 | 简单 | 本题 |
| 109. 有序链表转换二叉搜索树 | 中等 | 链表版本,找中点 |
| 110. 平衡二叉树 | 简单 | 检查平衡性 |
| 1382. 将二叉搜索树变平衡 | 中等 | 重构平衡BST |
九、总结
| 对比项 | 说明 |
|---|---|
| 代码行数 | 核心7行 |
| 时间复杂度 | O(n) |
| 空间复杂度 | O(log n) |
| 递归顺序 | 前序遍历(先根后左右) |
| 核心技巧 | 中间元素作为根,保证平衡 |
核心公式:
根节点 = nums[(begin + end) / 2]
左子树 = nums[begin...mid-1]
右子树 = nums[mid+1...end]
平衡的本质:
- 数组已排序
- 中间元素左边的都小于它,右边的都大于它
- 选取中间元素,左右子树节点数自然相等或差1
- 因此高度差 ≤ 1