LeetCode 108. 将有序数组转换为二叉搜索树(递归构建详解 + Java Python 实现)

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^4
  • nums 按 严格递增 顺序排列

解题思路

这道题要求将有序数组转换为 平衡二叉搜索树(BST)。平衡意味着左右子树的高度差不超过 1。

核心思想:递归 + 二分

由于数组是升序排列的,我们可以利用二分查找的思想:

  1. 选择中间元素作为根节点:这样左半部分的元素都小于根节点,右半部分的元素都大于根节点,自然满足 BST 的性质。
  2. 递归构建左右子树 :
    • 左子树由数组左半部分构建。
    • 右子树由数组右半部分构建。
  3. 递归终止条件 :当 left > right 时,说明没有元素需要构建,返回 None(或 null)。

为什么选择中间元素作为根节点可以保证平衡?

因为每次都将数组均匀地分成两部分,左子树和右子树的节点数最多相差 1,所以整棵树是高度平衡的。

举例说明

以 nums = [-10,-3,0,5,9] 为例:

  1. 数组长度为 5,中间索引为 2,nums[2] = 0 作为根节点。
  2. 左半部分 [-10,-3],中间索引为 0,nums[0] = -10 作为左子树的根节点。
  3. 左子树的右半部分 [-3],nums[1] = -3 作为 -10 的右子节点。
  4. 右半部分 [5,9],中间索引为 3,nums[3] = 5 作为右子树的根节点。
  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


代码说明

  1. 主函数 sortedArrayToBST :调用辅助函数 buildTree,传入数组和初始的左右边界。
  2. 辅助函数 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

总结

这道题是递归构建二叉树的经典题目,核心要点:

  1. 选择中间元素作为根节点:保证左右子树平衡。
  2. 递归构建左右子树 :左子树范围 [left, mid-1],右子树范围 [mid+1, right]。
  3. 终止条件 :left > right 时返回 null。

关键点回顾:

  • 使用二分思想,选择中间元素作为根节点
  • 递归构建左右子树
  • 时间复杂度 O(n),空间复杂度 O(log n)
  • 数组长度为偶数时,选择不同的中间位置会得到不同的正确答案

这个递归模板也适用于其他树构建问题,建议熟练掌握。

如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!

相关推荐
小羊没烦恼!5 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
默_笙5 天前
🍙 给每个请求过安检:FastAPI 是怎么把校验写进类型注解的
python
俊昭喜喜里5 天前
java中的继承和多态的区别
java
小羊没烦恼!5 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
qq_426003965 天前
启动playwright录制codegen生成自动化测试脚本
python·自动化
譕痕5 天前
JSONObject与JSONArray封装数据格式区别
java·json
虎头金猫5 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖5 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
长沙三为智能科技5 天前
家政小程序开发从0到上线:五阶段交付流程与验收清单
python