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 题解和算法干货!

相关推荐
月华路1 小时前
G1 垃圾回收:脏卡队列、记忆集与并发精化线程机制
java·开发语言
XLYcmy1 小时前
京东 算法实习一面 下+手撕
c++·python·llm·概率论·数据处理·训练·codebert
虎头金猫2 小时前
如何在群晖NAS上通过Docker部署CloudSaver?群晖部署CloudSaver教程|聚合资源搜索并实现远程访问
运维·服务器·网络·python·docker·容器·pandas
从零开始学习人工智能2 小时前
WSL2部署CUDA12+cuDNN9踩坑实录:ONNX Runtime GPU推理环境搭建全流程
python
gugucoding2 小时前
47. 【Java】Java内存模型(JMM)与可见性
java·开发语言
m0_380743872 小时前
从零调用 Claude 教程
开发语言·python·node.js
zww89491112 小时前
校园跑腿系统开发实战指南:从需求分析到上线部署全流程解析
java
Fluxart.ai3 小时前
电商商品图审核怎么自动化?规则引擎、人工复核与发布门禁
java·前端·自动化
min(a,b)3 小时前
学习第17天:Agent Memory 与 MCP 协议
python·学习
名字还没想好☜3 小时前
Java 线上内存泄漏排查实战:jmap 导堆、MAT 找 GC Roots 与四类常见泄漏
java·开发语言·jvm·内存泄漏