力扣解题-108. 将有序数组转换为二叉搜索树

0 阅读8分钟

力扣解题-108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。

示例 1:

image.png

输入:nums = [-10,-3,0,5,9]

输出:[0,-3,9,-10,null,5]

解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案。

示例 2:

image.png

输入: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的核心规律:

  1. BST与升序数组的关系:升序数组是BST的中序遍历结果,要构建平衡BST,需让每个节点的左右子树节点数尽可能均衡;
  2. 分治核心思想:将数组划分为“左区间-中值-右区间”,中值作为根节点(保证左右子树节点数差≤1),递归处理左右区间构建子树;
  3. 平衡保证:每次选中间元素为根,左/右子树的节点数最多相差1,最终构建的树是高度平衡的(任意节点的左右子树高度差≤1)。
具体执行逻辑
  1. 递归入口:调用buildBST(nums, 0, nums.length-1),以整个数组为初始区间构建树;
  2. 递归终止条件:当left > right时(区间无元素),返回null(空节点);
  3. 选择根节点
    • 计算区间中间索引mid = left + (right - left)/2(避免(left+right)/2的整数溢出);
    • nums[mid]为值创建根节点TreeNode
  4. 递归构建子树
    • 左子树:递归处理左区间[left, mid-1],结果赋值给root.left
    • 右子树:递归处理右区间[mid+1, right],结果赋值给root.right
  5. 返回结果:返回当前根节点,逐层构建完整的平衡BST。
执行流程可视化(以示例1 nums=[-10,-3,0,5,9]为例)
递归层级区间[left,right]mid值根节点值左子树区间右子树区间构建结果
1[0,4]20[0,1][3,4]根节点0
2(左)[0,1]0-10[][1,1]0的左子节点-10
3(右)[1,1]1-3[][]-10的右子节点-3
2(右)[3,4]35[][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);
  • 优势:
    1. 分治思想天然适配平衡BST的构建,逻辑简洁且效率最优;
    2. 无需额外空间,仅通过递归划分区间即可完成构建;
    3. 代码量少,符合分治算法的经典范式。
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;
}
核心逻辑说明
  1. 右中值计算mid = left + (right - left + 1)/2,例如区间[0,1](元素[1,3]),mid=0+(1-0+1)/2=1,选3作为根节点(对应示例2的正确答案[3,1]);
  2. 结构差异:与原解法相比,构建的树节点分布右偏,但仍满足平衡要求;
  3. 结果合法性:题目允许多种正确答案,该解法的输出同样符合要求。
性能说明
  • 时间复杂度:O(n)(与原解法一致);
  • 空间复杂度:O(logn)(与原解法一致);
  • 优势:
    1. 适配需要右偏根节点的场景,覆盖更多正确答案;
    2. 仅修改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;
}
核心逻辑说明
  1. 辅助类设计NodeInfo存储区间范围、父节点和子树类型,明确当前节点是父节点的左/右子树;
  2. 栈的处理顺序:栈是后进先出,因此先压入右区间、再压入左区间,保证处理顺序与递归一致(先左后右);
  3. 根节点处理:无父节点的区间对应根节点,单独赋值;
  4. 子树赋值:根据isLeft标记,将当前节点赋值给父节点的左/右子节点。
性能说明
  • 时间复杂度:O(n)(每个元素仅处理一次);
  • 空间复杂度:O(logn)(栈存储的区间数等于树的高度);
  • 优势:
    1. 非递归实现,避免极端场景下的栈溢出;
    2. 清晰展示递归的底层执行过程,帮助理解分治逻辑;
  • 劣势:代码量多于递归法,需设计辅助类,新手入门门槛略高。

总结

  1. 分治递归法(第一次解答):O(n)时间+O(logn)空间,选择左中值为根,逻辑简洁、效率最优,是本题的核心解法;
  2. 右中值递归法:O(n)时间+O(logn)空间,仅修改mid计算方式,构建右偏平衡BST,覆盖更多正确答案;
  3. 迭代法:O(n)时间+O(logn)空间,非递归实现,避免栈溢出,适合理解递归底层逻辑;
  4. 关键技巧
    • 核心思想:升序数组转平衡BST的核心是“分治选中值为根”,保证左右子树节点数均衡;
    • mid计算:优先使用left + (right-left)/2避免溢出,可选右中值适配不同结构;
    • 平衡保证:每次选中间元素为根,天然满足高度平衡的要求。