力扣解题-108. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案。
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 10⁴
-10⁴ <= nums[i] <= 10⁴
nums 按 严格递增 顺序排列
Related Topics
树、二叉搜索树、数组、分治、二叉树
第一次解答
解题思路
核心方法:分治+中值选根法,利用BST“中序遍历为升序序列”的特性,通过分治思想每次选择数组区间的中间元素作为根节点,左半区间构建左子树、右半区间构建右子树,天然保证二叉树的平衡性(左右子树高度差不超过1),时间复杂度O(n)、空间复杂度O(logn)(递归栈深度),是本题的最优解法。
核心逻辑拆解
有序数组转平衡BST的核心规律:
- BST与升序数组的关系:升序数组是BST的中序遍历结果,要构建平衡BST,需让每个节点的左右子树节点数尽可能均衡;
- 分治核心思想:将数组划分为“左区间-中值-右区间”,中值作为根节点(保证左右子树节点数差≤1),递归处理左右区间构建子树;
- 平衡保证:每次选中间元素为根,左/右子树的节点数最多相差1,最终构建的树是高度平衡的(任意节点的左右子树高度差≤1)。
具体执行逻辑
- 递归入口:调用
buildBST(nums, 0, nums.length-1),以整个数组为初始区间构建树; - 递归终止条件:当
left > right时(区间无元素),返回null(空节点); - 选择根节点:
- 计算区间中间索引
mid = left + (right - left)/2(避免(left+right)/2的整数溢出); - 以
nums[mid]为值创建根节点TreeNode;
- 计算区间中间索引
- 递归构建子树:
- 左子树:递归处理左区间
[left, mid-1],结果赋值给root.left; - 右子树:递归处理右区间
[mid+1, right],结果赋值给root.right;
- 左子树:递归处理左区间
- 返回结果:返回当前根节点,逐层构建完整的平衡BST。
执行流程可视化(以示例1 nums=[-10,-3,0,5,9]为例)
| 递归层级 | 区间[left,right] | mid值 | 根节点值 | 左子树区间 | 右子树区间 | 构建结果 |
|---|---|---|---|---|---|---|
| 1 | [0,4] | 2 | 0 | [0,1] | [3,4] | 根节点0 |
| 2(左) | [0,1] | 0 | -10 | [] | [1,1] | 0的左子节点-10 |
| 3(右) | [1,1] | 1 | -3 | [] | [] | -10的右子节点-3 |
| 2(右) | [3,4] | 3 | 5 | [] | [4,4] | 0的右子节点9(mid=3?注:原数组索引3是5,4是9,mid=3+(4-3)/2=3,根为5,5的右子为9) |
注:示例1的另一种正确答案是选mid=2(值0)为根,左子树[-10,-3]选mid=1(值-3),右子树[5,9]选mid=4(值9),体现了“中间元素”可选择左中值/右中值的灵活性。
关键细节说明
- mid计算优化:使用
left + (right - left)/2而非(left + right)/2,避免left+right超出int范围导致的溢出(如left=1e4,right=1e4时无问题,但大数场景更安全); - 平衡特性保证:每次选中间元素为根,左/右子树的节点数差≤1,因此树的高度为
log2(n),是严格平衡的; - 递归终止条件:
left > right是核心,确保空区间返回null,避免数组越界; - 结果不唯一:由于“中间元素”可选择左中值(
mid = left + (right-left)/2)或右中值(mid = left + (right-left+1)/2),因此正确答案不唯一,均符合平衡BST要求。
性能说明
- 时间复杂度:O(n)(每个元素仅被访问一次,构建一个节点);
- 空间复杂度:O(logn)(递归栈深度等于树的高度,平衡BST的高度为log₂n);
- 优势:
- 分治思想天然适配平衡BST的构建,逻辑简洁且效率最优;
- 无需额外空间,仅通过递归划分区间即可完成构建;
- 代码量少,符合分治算法的经典范式。
public TreeNode sortedArrayToBST(int[] nums) {
return buildBST(nums,0,nums.length-1);
}
public TreeNode buildBST(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=buildBST(nums,left,mid-1);
root.right=buildBST(nums,mid+1,right);
return root;
}
示例解答
解题思路
解法1:选择右中值作为根节点(适配偶数长度区间)
核心方法:修改mid的计算方式为left + (right - left + 1)/2,选择区间的右中值作为根节点,构建的BST结构与原解法不同,但同样满足平衡要求,适配需要右偏根节点的场景。
代码实现
public TreeNode sortedArrayToBST(int[] nums) {
return buildBST(nums, 0, nums.length - 1);
}
private TreeNode buildBST(int[] nums, int left, int right) {
if (left > right) {
return null;
}
// 选择右中值作为根节点(偶数长度时选右侧中间元素)
int mid = left + (right - left + 1) / 2;
TreeNode root = new TreeNode(nums[mid]);
// 递归构建左、右子树
root.left = buildBST(nums, left, mid - 1);
root.right = buildBST(nums, mid + 1, right);
return root;
}
核心逻辑说明
- 右中值计算:
mid = left + (right - left + 1)/2,例如区间[0,1](元素[1,3]),mid=0+(1-0+1)/2=1,选3作为根节点(对应示例2的正确答案[3,1]); - 结构差异:与原解法相比,构建的树节点分布右偏,但仍满足平衡要求;
- 结果合法性:题目允许多种正确答案,该解法的输出同样符合要求。
性能说明
- 时间复杂度:O(n)(与原解法一致);
- 空间复杂度:O(logn)(与原解法一致);
- 优势:
- 适配需要右偏根节点的场景,覆盖更多正确答案;
- 仅修改mid计算方式,逻辑无其他变化,易理解。
解法2:迭代法(非递归,避免栈溢出)
核心方法:使用栈模拟递归过程,存储待处理的区间(left, right)和对应的父节点、子树类型(左/右),通过迭代方式构建平衡BST,避免递归栈溢出风险(如数组长度接近1e4时,递归栈深度约14,无溢出风险,但迭代法更通用)。
代码实现
// 定义辅助类,存储区间和父节点、子树类型
class NodeInfo {
int left;
int right;
TreeNode parent;
boolean isLeft; // true: 作为父节点的左子树;false: 作为右子树
public NodeInfo(int left, int right, TreeNode parent, boolean isLeft) {
this.left = left;
this.right = right;
this.parent = parent;
this.isLeft = isLeft;
}
}
public TreeNode sortedArrayToBST(int[] nums) {
if (nums == null || nums.length == 0) {
return null;
}
Stack<NodeInfo> stack = new Stack<>();
// 根节点区间:[0, nums.length-1],无父节点
TreeNode root = null;
stack.push(new NodeInfo(0, nums.length - 1, null, false));
while (!stack.isEmpty()) {
NodeInfo info = stack.pop();
int left = info.left;
int right = info.right;
if (left > right) {
continue;
}
// 计算中间索引,构建当前节点
int mid = left + (right - left) / 2;
TreeNode curr = new TreeNode(nums[mid]);
// 确定当前节点的父节点关系
if (info.parent == null) {
// 无父节点,即为根节点
root = curr;
} else {
if (info.isLeft) {
info.parent.left = curr;
} else {
info.parent.right = curr;
}
}
// 先压入右区间(栈是后进先出,保证先处理左区间)
stack.push(new NodeInfo(mid + 1, right, curr, false));
// 再压入左区间
stack.push(new NodeInfo(left, mid - 1, curr, true));
}
return root;
}
核心逻辑说明
- 辅助类设计:
NodeInfo存储区间范围、父节点和子树类型,明确当前节点是父节点的左/右子树; - 栈的处理顺序:栈是后进先出,因此先压入右区间、再压入左区间,保证处理顺序与递归一致(先左后右);
- 根节点处理:无父节点的区间对应根节点,单独赋值;
- 子树赋值:根据
isLeft标记,将当前节点赋值给父节点的左/右子节点。
性能说明
- 时间复杂度:O(n)(每个元素仅处理一次);
- 空间复杂度:O(logn)(栈存储的区间数等于树的高度);
- 优势:
- 非递归实现,避免极端场景下的栈溢出;
- 清晰展示递归的底层执行过程,帮助理解分治逻辑;
- 劣势:代码量多于递归法,需设计辅助类,新手入门门槛略高。
总结
- 分治递归法(第一次解答):O(n)时间+O(logn)空间,选择左中值为根,逻辑简洁、效率最优,是本题的核心解法;
- 右中值递归法:O(n)时间+O(logn)空间,仅修改mid计算方式,构建右偏平衡BST,覆盖更多正确答案;
- 迭代法:O(n)时间+O(logn)空间,非递归实现,避免栈溢出,适合理解递归底层逻辑;
- 关键技巧:
- 核心思想:升序数组转平衡BST的核心是“分治选中值为根”,保证左右子树节点数均衡;
- mid计算:优先使用
left + (right-left)/2避免溢出,可选右中值适配不同结构; - 平衡保证:每次选中间元素为根,天然满足高度平衡的要求。