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

相关推荐
xcl09253 小时前
幼儿托育系统开发实战:从需求分析到上线全流程指南
java·大数据·需求分析
吴声子夜歌3 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
小小猪的春天3 小时前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
find1star3 小时前
LeetCode 141:环形链表
java·算法·leetcode·链表
LB21123 小时前
力扣160 21 86
算法·leetcode·职场和发展
今天AI了吗4 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee
benchmark_cc4 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
许彰午4 小时前
27-AuthService登录链路
java·低代码·架构
泡海椒4 小时前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
a187927218315 小时前
【算法】动态规划第二篇:双序列 DP 三课——继承、计步与断链
算法·leetcode·动态规划·dp·回溯·暴力·算法讲解