当前位置: 首页 > news >正文

【LeetCode】108. 将有序数组转换为二叉搜索树

文章目录

  • 108. 将有序数组转换为二叉搜索树
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题分析
      • 算法思想
      • 方法一:递归(中间偏左)
      • 方法二:递归(中间偏右)
      • 方法三:迭代法(栈模拟)
      • 方法四:中序遍历模拟
      • 复杂度对比
      • 关键点总结
      • 扩展问题
      • 相关题目
    • 完整题解代码

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

解题思路

问题分析

这是一道经典的二叉搜索树(BST)构建问题。关键点:

  1. 输入特点:数组已经按升序排列
  2. 输出要求:构建平衡二叉搜索树
  3. 核心洞察:有序数组的中序遍历恰好是BST的中序遍历结果

算法思想

由于数组已排序,我们可以利用分治思想

  1. 选择中间元素作为根节点(保证平衡)
  2. 左半部分递归构建左子树
  3. 右半部分递归构建右子树

这样构建的树自然满足:

  • BST性质:左子树 < 根 < 右子树
  • 平衡性:左右子树高度差不超过1

方法一:递归(中间偏左)

核心思想:每次选择中间位置(偏左)作为根节点

graph TDA[开始: nums=[-10,-3,0,5,9]] --> B[选择中间索引 mid=2, 值=0]B --> C[0作为根节点]C --> D[左子数组: -10,-3]C --> E[右子数组: 5,9]D --> F[选择mid=0, 值=-3]F --> G[-3作为左子树根]G --> H[左: -10]G --> I[右: null]E --> J[选择mid=0, 值=5]J --> K[5作为右子树根]K --> L[左: null]K --> M[右: 9]style A fill:#e1f5ffstyle C fill:#bbdefbstyle G fill:#90caf9style K fill:#90caf9

算法步骤

flowchart TDStart([输入: nums, left, right]) --> Check{left > right?}Check -->|是| ReturnNil[返回 nil]Check -->|否| CalcMid[计算中间索引<br/>mid = left + right / 2]CalcMid --> CreateNode[创建根节点<br/>root = &TreeNode{Val: nums[mid]}]CreateNode --> BuildLeft[递归构建左子树<br/>root.Left = sortedArrayToBST nums, left, mid-1]BuildLeft --> BuildRight[递归构建右子树<br/>root.Right = sortedArrayToBST nums, mid+1, right]BuildRight --> Return([返回 root])ReturnNil --> End([结束])Return --> Endstyle Start fill:#e8f5e9style Check fill:#fff3e0style CreateNode fill:#bbdefbstyle BuildLeft fill:#c8e6c9style BuildRight fill:#c8e6c9style Return fill:#c5cae9

时间复杂度:O(n) - 每个节点访问一次
空间复杂度:O(log n) - 递归栈深度

方法二:递归(中间偏右)

核心思想:选择中间偏右的位置作为根节点

mid := left + (right - left + 1) / 2

这样会生成另一种平衡的BST结构。

方法三:迭代法(栈模拟)

核心思想:使用栈模拟递归过程

flowchart TDStart([输入: nums]) --> Init[初始化栈<br/>压入 0, len-1, nil, true]Init --> Loop{栈非空?}Loop -->|否| End([返回根节点])Loop -->|是| Pop[弹出栈顶<br/>left, right, parent, isLeft]Pop --> Check{left > right?}Check -->|是| LoopCheck -->|否| CalcMid[mid = left + right / 2]CalcMid --> CreateNode[创建节点<br/>node = &TreeNode{Val: nums[mid]}]CreateNode --> CheckParent{parent == nil?}CheckParent -->|是| SetRoot[设置为根节点]CheckParent -->|否| CheckLeft{isLeft?}CheckLeft -->|是| SetLeft[parent.Left = node]CheckLeft -->|否| SetRight[parent.Right = node]SetRoot --> PushRightSetLeft --> PushRightSetRight --> PushRight[压入右子树参数<br/>mid+1, right, node, false]PushRight --> PushLeft[压入左子树参数<br/>left, mid-1, node, true]PushLeft --> Loopstyle Start fill:#e8f5e9style CreateNode fill:#bbdefbstyle Loop fill:#fff3e0style End fill:#c5cae9

时间复杂度:O(n)
空间复杂度:O(log n)

方法四:中序遍历模拟

核心思想:按中序遍历的顺序构建BST

这种方法需要提前知道树的大小,然后在中序遍历位置填充节点值。

复杂度对比

方法时间复杂度空间复杂度特点
递归(中间偏左)O(n)O(log n)代码简洁,易理解
递归(中间偏右)O(n)O(log n)生成不同的平衡树
迭代法O(n)O(log n)避免递归栈溢出
中序遍历O(n)O(log n)模拟BST构建过程

关键点总结

  1. 选择中点:保证平衡性的关键
  2. 递归边界:left > right 时返回 nil
  3. 分治思想:问题规模减半
  4. BST性质:利用数组有序性

扩展问题

  1. 如何验证构建的树是否平衡?
  2. 如何构建最小高度的BST?
  3. 如果数组有重复元素怎么处理?
  4. 如何构建完全二叉搜索树?

相关题目

  • LeetCode 109:有序链表转换二叉搜索树
  • LeetCode 110:平衡二叉树
  • LeetCode 1382:将二叉搜索树变平衡

完整题解代码

package mainimport ("fmt""math""strings"
)// TreeNode 二叉树节点定义
type TreeNode struct {Val   intLeft  *TreeNodeRight *TreeNode
}// ==================== 方法一:递归(中间偏左)====================
// 时间复杂度:O(n),每个元素访问一次
// 空间复杂度:O(log n),递归栈深度
func sortedArrayToBST(nums []int) *TreeNode {return buildBST(nums, 0, len(nums)-1)
}func buildBST(nums []int, left, right int) *TreeNode {// 递归终止条件if left > right {return nil}// 选择中间位置(偏左)作为根节点mid := left + (right-left)/2root := &TreeNode{Val: nums[mid]}// 递归构建左右子树root.Left = buildBST(nums, left, mid-1)root.Right = buildBST(nums, mid+1, right)return root
}// ==================== 方法二:递归(中间偏右)====================
// 选择中间偏右的元素作为根节点,会生成不同的平衡BST
func sortedArrayToBST2(nums []int) *TreeNode {return buildBST2(nums, 0, len(nums)-1)
}func buildBST2(nums []int, left, right int) *TreeNode {if left > right {return nil}// 选择中间位置(偏右)作为根节点mid := left + (right-left+1)/2root := &TreeNode{Val: nums[mid]}root.Left = buildBST2(nums, left, mid-1)root.Right = buildBST2(nums, mid+1, right)return root
}// ==================== 方法三:迭代法(栈模拟)====================
// 使用栈模拟递归过程
type StackNode struct {left   intright  intparent *TreeNodeisLeft bool
}func sortedArrayToBST3(nums []int) *TreeNode {if len(nums) == 0 {return nil}var root *TreeNodestack := []StackNode{{0, len(nums) - 1, nil, true}}for len(stack) > 0 {// 弹出栈顶元素curr := stack[len(stack)-1]stack = stack[:len(stack)-1]if curr.left > curr.right {continue}// 创建当前节点mid := curr.left + (curr.right-curr.left)/2node := &TreeNode{Val: nums[mid]}// 处理父节点连接if curr.parent == nil {root = node} else if curr.isLeft {curr.parent.Left = node} else {curr.parent.Right = node}// 先压入右子树,再压入左子树(栈是后进先出)stack = append(stack, StackNode{mid + 1, curr.right, node, false})stack = append(stack, StackNode{curr.left, mid - 1, node, true})}return root
}// ==================== 方法四:中序遍历模拟 ====================
// 按中序遍历的顺序构建BST
func sortedArrayToBST4(nums []int) *TreeNode {if len(nums) == 0 {return nil}idx := 0return inorderBuild(nums, &idx, 0, len(nums)-1)
}func inorderBuild(nums []int, idx *int, left, right int) *TreeNode {if left > right {return nil}mid := left + (right-left)/2// 先构建左子树leftNode := inorderBuild(nums, idx, left, mid-1)// 创建根节点root := &TreeNode{Val: nums[*idx]}*idx++root.Left = leftNode// 再构建右子树root.Right = inorderBuild(nums, idx, mid+1, right)return root
}// ==================== 辅助函数 ====================// 中序遍历验证BST
func inorderTraversal(root *TreeNode) []int {if root == nil {return []int{}}result := []int{}result = append(result, inorderTraversal(root.Left)...)result = append(result, root.Val)result = append(result, inorderTraversal(root.Right)...)return result
}// 层序遍历(用于显示树结构)
func levelOrder(root *TreeNode) []interface{} {if root == nil {return []interface{}{}}result := []interface{}{}queue := []*TreeNode{root}for len(queue) > 0 {node := queue[0]queue = queue[1:]if node == nil {result = append(result, nil)} else {result = append(result, node.Val)queue = append(queue, node.Left)queue = append(queue, node.Right)}}// 移除末尾的 nilfor len(result) > 0 && result[len(result)-1] == nil {result = result[:len(result)-1]}return result
}// 检查是否为平衡二叉树
func isBalanced(root *TreeNode) bool {return checkHeight(root) != -1
}func checkHeight(root *TreeNode) int {if root == nil {return 0}leftHeight := checkHeight(root.Left)if leftHeight == -1 {return -1}rightHeight := checkHeight(root.Right)if rightHeight == -1 {return -1}if abs(leftHeight-rightHeight) > 1 {return -1}return max(leftHeight, rightHeight) + 1
}// 检查是否为BST
func isValidBST(root *TreeNode) bool {return validateBST(root, math.MinInt64, math.MaxInt64)
}func validateBST(root *TreeNode, min, max int) bool {if root == nil {return true}if root.Val <= min || root.Val >= max {return false}return validateBST(root.Left, min, root.Val) && validateBST(root.Right, root.Val, max)
}// 获取树的高度
func getHeight(root *TreeNode) int {if root == nil {return 0}return max(getHeight(root.Left), getHeight(root.Right)) + 1
}// 树形打印
func printTree(root *TreeNode, prefix string, isLeft bool) {if root == nil {return}fmt.Print(prefix)if isLeft {fmt.Print("├── ")} else {fmt.Print("└── ")}fmt.Println(root.Val)if root.Left != nil || root.Right != nil {if root.Left != nil {printTree(root.Left, prefix+getTreePrefix(isLeft, true), true)} else {fmt.Println(prefix + getTreePrefix(isLeft, true) + "├── nil")}if root.Right != nil {printTree(root.Right, prefix+getTreePrefix(isLeft, false), false)} else {fmt.Println(prefix + getTreePrefix(isLeft, false) + "└── nil")}}
}func getTreePrefix(isLeft, hasNext bool) string {if isLeft {return "│   "}return "    "
}func abs(x int) int {if x < 0 {return -x}return x
}func max(a, b int) int {if a > b {return a}return b
}// ==================== 测试函数 ====================func testCase(name string, nums []int) {fmt.Printf("\n========== %s ==========\n", name)fmt.Printf("输入: %v\n", nums)// 方法一:中间偏左root1 := sortedArrayToBST(nums)fmt.Println("\n方法一(中间偏左):")fmt.Printf("层序遍历: %v\n", levelOrder(root1))fmt.Printf("中序遍历: %v\n", inorderTraversal(root1))fmt.Printf("是否平衡: %v\n", isBalanced(root1))fmt.Printf("是否BST: %v\n", isValidBST(root1))fmt.Printf("树高度: %d\n", getHeight(root1))fmt.Println("树结构:")printTree(root1, "", false)// 方法二:中间偏右root2 := sortedArrayToBST2(nums)fmt.Println("\n方法二(中间偏右):")fmt.Printf("层序遍历: %v\n", levelOrder(root2))fmt.Printf("中序遍历: %v\n", inorderTraversal(root2))fmt.Printf("是否平衡: %v\n", isBalanced(root2))// 方法三:迭代法root3 := sortedArrayToBST3(nums)fmt.Println("\n方法三(迭代法):")fmt.Printf("层序遍历: %v\n", levelOrder(root3))fmt.Printf("中序遍历: %v\n", inorderTraversal(root3))fmt.Printf("是否平衡: %v\n", isBalanced(root3))// 方法四:中序遍历root4 := sortedArrayToBST4(nums)fmt.Println("\n方法四(中序遍历):")fmt.Printf("层序遍历: %v\n", levelOrder(root4))fmt.Printf("中序遍历: %v\n", inorderTraversal(root4))fmt.Printf("是否平衡: %v\n", isBalanced(root4))
}// ==================== 扩展功能 ====================// 从BST转回有序数组
func bstToSortedArray(root *TreeNode) []int {return inorderTraversal(root)
}// 获取BST中第k小的元素
func kthSmallest(root *TreeNode, k int) int {arr := inorderTraversal(root)if k > 0 && k <= len(arr) {return arr[k-1]}return -1
}// 将BST转换为更平衡的BST(重新构建)
func balanceBST(root *TreeNode) *TreeNode {arr := inorderTraversal(root)return sortedArrayToBST(arr)
}// 比较两种方法生成的树结构差异
func compareTreeStructures(nums []int) {fmt.Printf("\n========== 比较不同方法生成的树结构 ==========\n")fmt.Printf("输入数组: %v\n", nums)root1 := sortedArrayToBST(nums)root2 := sortedArrayToBST2(nums)fmt.Println("\n中间偏左策略:")printTree(root1, "", false)fmt.Println("\n中间偏右策略:")printTree(root2, "", false)fmt.Printf("\n两棵树的中序遍历相同: %v\n",strings.Trim(strings.Join(strings.Fields(fmt.Sprint(inorderTraversal(root1))), ","), "[]") ==strings.Trim(strings.Join(strings.Fields(fmt.Sprint(inorderTraversal(root2))), ","), "[]"))
}// 性能测试
func performanceTest() {fmt.Println("\n========== 性能测试 ==========")sizes := []int{100, 1000, 10000}for _, size := range sizes {nums := make([]int, size)for i := 0; i < size; i++ {nums[i] = i}fmt.Printf("\n数组大小: %d\n", size)// 测试方法一root1 := sortedArrayToBST(nums)fmt.Printf("方法一 - 树高度: %d, 期望高度: %d\n",getHeight(root1), int(math.Ceil(math.Log2(float64(size+1)))))// 测试方法二root2 := sortedArrayToBST2(nums)fmt.Printf("方法二 - 树高度: %d, 期望高度: %d\n",getHeight(root2), int(math.Ceil(math.Log2(float64(size+1)))))}
}func main() {// 测试用例1:示例1testCase("测试用例1:基本情况", []int{-10, -3, 0, 5, 9})// 测试用例2:示例2testCase("测试用例2:两个元素", []int{1, 3})// 测试用例3:单个元素testCase("测试用例3:单个元素", []int{1})// 测试用例4:奇数个元素testCase("测试用例4:奇数个元素", []int{1, 2, 3, 4, 5, 6, 7})// 测试用例5:偶数个元素testCase("测试用例5:偶数个元素", []int{1, 2, 3, 4, 5, 6})// 测试用例6:连续数字testCase("测试用例6:连续数字", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})// 测试用例7:负数testCase("测试用例7:负数", []int{-10, -5, -3, 0, 5, 10})// 扩展功能测试fmt.Println("\n========== 扩展功能测试 ==========")nums := []int{1, 2, 3, 4, 5, 6, 7}root := sortedArrayToBST(nums)fmt.Printf("原数组: %v\n", nums)fmt.Printf("BST转回数组: %v\n", bstToSortedArray(root))fmt.Printf("第3小的元素: %d\n", kthSmallest(root, 3))// 比较不同构建策略compareTreeStructures([]int{1, 2, 3, 4, 5, 6, 7})// 性能测试performanceTest()
}
http://www.dtcms.com/a/606270.html

相关文章:

  • 12.vector—string(下)
  • 具身智能数据采集全方案:动作捕捉技术驱动机器人拟人化进阶
  • 公司网站地图怎么做长沙网站托管优化
  • 网站免费创建雅虎搜索
  • 多通道手腕压力脉搏波信号
  • 眉县网站建设wordpress首页flash
  • 贪心算法实验2
  • C语言在线编译器开发 | 提高编译效率与用户体验的创新技术
  • MD5 校验脚本
  • 重生归来,我要成功 Python 高手--day35 深度学习 Pytorch
  • 马云有没有学过做网站百度收录时间
  • 企业网站的规划与建设ppt建设一个打鱼游戏网站
  • 在 Linux Ubuntu 24.04 安装 IntelliJ IDEA
  • 自适应网站建设方案建设网站 请示 报告
  • 有哪些做网站的品牌ios开发app
  • C语言编译器电脑版 | 提供高效编译体验,轻松学习与开发
  • 容器访问某个链接中断后面又正常,socket
  • 构建现代应用的9个Python GUI库
  • 做网站业务的怎么寻找客户做网站公司哪家强
  • 【第1章>第6节】基于FPGA的图像膨胀处理算法的Verilog实现
  • 网站开发对企业的关键化妆品首页设计
  • 基于图的可解释性推荐综述
  • Nginx搭建RTMP点播流媒体服务器步骤详解,Nginx+RTMP+OBS推流搭建流媒体服务器
  • 东莞建设网站官网住房和城乡网站平台系统设计公司
  • 具身智能-一文详解视觉-语言-动作(VLA)大模型(2)
  • 如何使用 Docker 打包一个简单的应用程序:简易指南
  • Hyper-V Windows 11 Pro x64 开局问题
  • 长沙外贸建站土地 水利 勘测设计 公司宣传册设计样本
  • Cursor区域限制解决方法, Cursor 提示:“Model not available“的原因
  • 自签名证书需要手动确认风险导致nginx转发无效问题