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

【LeetCode】109. 有序链表转换二叉搜索树

文章目录

  • 109. 有序链表转换二叉搜索树
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题分析
      • 方法一:快慢指针 + 递归(最优解)
      • 方法二:转换为数组
      • 方法三:中序遍历模拟(最巧妙)
      • 方法四:递归 + 计算长度
      • 复杂度对比
      • 关键技巧总结
      • 与108题对比
      • 扩展问题
      • 相关题目
    • 完整题解代码

109. 有序链表转换二叉搜索树

题目描述

给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为 平衡 二叉搜索树。

示例 1:

在这里插入图片描述

输入: head = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
解释: 一个可能的答案是[0,-3,9,-10,null,5],它表示所示的高度平衡的二叉搜索树。

示例 2:

输入: head = []
输出: []

提示:

  • head 中的节点数在[0, 2 * 104] 范围内
  • -10^5 <= Node.val <= 10^5

解题思路

问题分析

这道题是 LeetCode 108 的升级版,核心差异:

维度108题109题
数据结构有序数组有序链表
访问方式O(1)随机访问O(n)顺序访问
找中点直接计算索引需要遍历/快慢指针
难度提升⭐⭐⭐⭐⭐

关键挑战:链表无法像数组那样 O(1) 访问中间元素!

方法一:快慢指针 + 递归(最优解)

核心思想:使用快慢指针找链表中点,然后递归构建

graph TDA[输入: -10→-3→0→5→9] --> B[快慢指针找中点]B --> C[slow指向0, 断开链表]C --> D[左半: -10→-3]C --> E[中点: 0]C --> F[右半: 5→9]E --> G[创建根节点 val=0]D --> H[递归处理左半链表]F --> I[递归处理右半链表]H --> J[左子树: -3为根]I --> K[右子树: 5为根]style A fill:#e1f5ffstyle E fill:#bbdefbstyle G fill:#90caf9style J fill:#c8e6c9style K fill:#c8e6c9

快慢指针找中点技巧

输入: head链表
slow = head
fast = head
prev = nil
fast != nil &&
fast.Next != nil?
找到中点: slow
prev = slow
slow = slow.Next
fast = fast.Next.Next
断开链表
prev.Next = nil
返回: 左半链表, 中点, 右半链表

算法步骤

  1. 边界处理:链表为空或只有一个节点
  2. 找中点:快指针走2步,慢指针走1步
  3. 断开链表:将链表分为左半和右半
  4. 递归构建
    • 创建根节点(中点值)
    • 递归处理左半链表 → 左子树
    • 递归处理右半链表 → 右子树

代码实现

func sortedListToBST(head *ListNode) *TreeNode {if head == nil {return nil}if head.Next == nil {return &TreeNode{Val: head.Val}}// 快慢指针找中点slow, fast, prev := head, head, (*ListNode)(nil)for fast != nil && fast.Next != nil {prev = slowslow = slow.Nextfast = fast.Next.Next}// 断开链表prev.Next = nil// 构建树root := &TreeNode{Val: slow.Val}root.Left = sortedListToBST(head)      // 左半链表root.Right = sortedListToBST(slow.Next) // 右半链表return root
}

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

  • 每层递归需要 O(n) 时间找中点
  • 递归深度 O(log n)

空间复杂度:O(log n) - 递归栈

方法二:转换为数组

核心思想:先将链表转为数组,然后用108题的方法

flowchart LRA[链表: -10→-3→0→5→9] --> B[遍历转数组]B --> C[数组: -10,-3,0,5,9]C --> D[108题方法构建BST]D --> E[返回根节点]style A fill:#e1f5ffstyle C fill:#bbdefbstyle E fill:#c5cae9

优缺点分析

维度评价
实现难度⭐ 简单,复用108题代码
时间复杂度O(n) 遍历 + O(n) 构建 = O(n) ✅
空间复杂度O(n) 数组存储 ❌
适用场景空间充足、追求简洁

方法三:中序遍历模拟(最巧妙)

核心洞察:BST的中序遍历恰好是有序序列!

算法思路

  1. 先统计链表长度 n
  2. 按中序遍历顺序构建树(左-根-右)
  3. 用全局指针记录当前链表节点
统计链表长度 n=5
中序构建 0, n-1
递归左子树 0, 1
创建节点 val=链表当前值
指针后移
递归右子树 3, 4

为什么有效?

链表: -10 → -3 → 0 → 5 → 9↑
中序遍历顺序构建树时,依次消费链表节点

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

方法四:递归 + 计算长度

优化思路:避免每次都找中点

  1. 先遍历一次计算链表总长度
  2. 递归时传递子链表的起始位置和长度
  3. 根据长度计算中点位置

复杂度对比

方法时间复杂度空间复杂度优点缺点
快慢指针O(n log n)O(log n)直观易懂重复遍历
转数组O(n)O(n)简单快速额外空间
中序遍历O(n)O(log n)最优解较难理解
计算长度O(n)O(log n)避免重复需要预处理

关键技巧总结

  1. 快慢指针找中点

    slow, fast := head, head
    for fast != nil && fast.Next != nil {slow = slow.Nextfast = fast.Next.Next
    }
    // slow 指向中点
    
  2. 断开链表:需要记录 prev 指针

  3. 中序遍历技巧:全局指针顺序消费链表节点

  4. 长度计算优化:避免重复遍历

与108题对比

维度108题(数组)109题(链表)
找中点O(1)计算索引O(n)快慢指针
分割O(1)传递索引O(1)断开链表
总复杂度O(n)O(n log n) 或 O(n)
实现难度⭐⭐⭐⭐⭐

扩展问题

  1. 如果链表是循环链表怎么办?
  2. 如何在O(n)时间、O(1)空间完成?(不计递归栈)
  3. 如果要求构建完全二叉搜索树?
  4. 如何处理链表中有重复元素的情况?

相关题目

  • LeetCode 108:有序数组转BST(前置题)
  • LeetCode 110:平衡二叉树
  • LeetCode 876:链表的中间结点
  • LeetCode 1382:将BST变平衡

完整题解代码

package mainimport ("fmt""math"
)// ListNode 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// TreeNode 二叉树节点定义
type TreeNode struct {Val   intLeft  *TreeNodeRight *TreeNode
}// ==================== 方法一:快慢指针 + 递归 ====================
// 时间复杂度:O(n log n),每层递归O(n)找中点,深度O(log n)
// 空间复杂度:O(log n),递归栈深度
func sortedListToBST(head *ListNode) *TreeNode {// 边界条件if head == nil {return nil}if head.Next == nil {return &TreeNode{Val: head.Val}}// 使用快慢指针找中点slow, fast := head, headvar prev *ListNodefor fast != nil && fast.Next != nil {prev = slowslow = slow.Nextfast = fast.Next.Next}// 断开链表:prev.Next = nilif prev != nil {prev.Next = nil}// 创建根节点(中点)root := &TreeNode{Val: slow.Val}// 递归构建左右子树root.Left = sortedListToBST(head)       // 左半链表root.Right = sortedListToBST(slow.Next) // 右半链表return root
}// ==================== 方法二:转换为数组 ====================
// 时间复杂度:O(n),遍历一次链表 + 构建树
// 空间复杂度:O(n),数组存储
func sortedListToBST2(head *ListNode) *TreeNode {// 链表转数组nums := []int{}curr := headfor curr != nil {nums = append(nums, curr.Val)curr = curr.Next}// 使用108题的方法构建BSTreturn arrayToBST(nums, 0, len(nums)-1)
}func arrayToBST(nums []int, left, right int) *TreeNode {if left > right {return nil}mid := left + (right-left)/2root := &TreeNode{Val: nums[mid]}root.Left = arrayToBST(nums, left, mid-1)root.Right = arrayToBST(nums, mid+1, right)return root
}// ==================== 方法三:中序遍历模拟(最优解)====================
// 时间复杂度:O(n),每个节点访问一次
// 空间复杂度:O(log n),递归栈
func sortedListToBST3(head *ListNode) *TreeNode {// 计算链表长度length := 0curr := headfor curr != nil {length++curr = curr.Next}// 使用全局指针,按中序遍历顺序消费链表节点return inorderBuild(&head, 0, length-1)
}func inorderBuild(head **ListNode, left, right int) *TreeNode {if left > right {return nil}mid := left + (right-left)/2// 先构建左子树(中序遍历:左-根-右)leftTree := inorderBuild(head, left, mid-1)// 创建根节点,消费当前链表节点root := &TreeNode{Val: (*head).Val}*head = (*head).Next // 指针后移// 再构建右子树root.Left = leftTreeroot.Right = inorderBuild(head, mid+1, right)return root
}// ==================== 方法四:递归 + 计算长度优化 ====================
// 时间复杂度:O(n)
// 空间复杂度:O(log n)
func sortedListToBST4(head *ListNode) *TreeNode {// 计算链表长度length := getLength(head)return buildWithLength(head, length)
}func getLength(head *ListNode) int {length := 0for head != nil {length++head = head.Next}return length
}func buildWithLength(head *ListNode, length int) *TreeNode {if length == 0 {return nil}if length == 1 {return &TreeNode{Val: head.Val}}// 找到中点位置mid := length / 2// 移动到中点curr := headfor i := 0; i < mid; i++ {curr = curr.Next}// 创建根节点root := &TreeNode{Val: curr.Val}// 递归构建左右子树root.Left = buildWithLength(head, mid)root.Right = buildWithLength(curr.Next, length-mid-1)return root
}// ==================== 辅助函数 ====================// 创建链表
func createList(nums []int) *ListNode {if len(nums) == 0 {return nil}head := &ListNode{Val: nums[0]}curr := headfor i := 1; i < len(nums); i++ {curr.Next = &ListNode{Val: nums[i]}curr = curr.Next}return head
}// 打印链表
func printList(head *ListNode) {fmt.Print("[")for head != nil {fmt.Print(head.Val)if head.Next != nil {fmt.Print(" -> ")}head = head.Next}fmt.Print("]")
}// 中序遍历验证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.Print("输入链表: ")head := createList(nums)printList(head)fmt.Println()// 方法一:快慢指针head1 := createList(nums)root1 := sortedListToBST(head1)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)// 方法二:转数组head2 := createList(nums)root2 := sortedListToBST2(head2)fmt.Println("\n方法二(转数组):")fmt.Printf("层序遍历: %v\n", levelOrder(root2))fmt.Printf("中序遍历: %v\n", inorderTraversal(root2))fmt.Printf("是否平衡: %v\n", isBalanced(root2))// 方法三:中序遍历(最优解)head3 := createList(nums)root3 := sortedListToBST3(head3)fmt.Println("\n方法三(中序遍历-最优解):")fmt.Printf("层序遍历: %v\n", levelOrder(root3))fmt.Printf("中序遍历: %v\n", inorderTraversal(root3))fmt.Printf("是否平衡: %v\n", isBalanced(root3))// 方法四:计算长度优化head4 := createList(nums)root4 := sortedListToBST4(head4)fmt.Println("\n方法四(计算长度优化):")fmt.Printf("层序遍历: %v\n", levelOrder(root4))fmt.Printf("中序遍历: %v\n", inorderTraversal(root4))fmt.Printf("是否平衡: %v\n", isBalanced(root4))
}// ==================== 扩展功能 ====================// 比较108题和109题的性能差异
func compareWithArray() {fmt.Println("\n========== 108题 vs 109题性能对比 ==========")sizes := []int{100, 1000, 5000}for _, size := range sizes {// 生成数据nums := make([]int, size)for i := 0; i < size; i++ {nums[i] = i}fmt.Printf("\n数据规模: %d\n", size)// 108题:数组方式(理论最优)root1 := arrayToBST(nums, 0, len(nums)-1)fmt.Printf("108题(数组): 树高度=%d, 理论高度=%d\n",getHeight(root1), int(math.Ceil(math.Log2(float64(size+1)))))// 109题方法一:快慢指针head := createList(nums)root2 := sortedListToBST(head)fmt.Printf("109题(快慢指针): 树高度=%d\n", getHeight(root2))// 109题方法三:中序遍历head = createList(nums)root3 := sortedListToBST3(head)fmt.Printf("109题(中序遍历): 树高度=%d\n", getHeight(root3))}
}// 链表中点查找演示
func demonstrateFindMiddle() {fmt.Println("\n========== 快慢指针找中点演示 ==========")testCases := [][]int{{1, 2, 3, 4, 5},    // 奇数个{1, 2, 3, 4, 5, 6}, // 偶数个{1},                // 单个{1, 2},             // 两个}for _, nums := range testCases {head := createList(nums)fmt.Print("\n链表: ")printList(head)fmt.Println()// 找中点slow, fast := head, headvar prev *ListNodefor fast != nil && fast.Next != nil {prev = slowslow = slow.Nextfast = fast.Next.Next}fmt.Printf("中点: %d\n", slow.Val)if prev != nil {fmt.Printf("中点前一个: %d\n", prev.Val)}}
}// 验证所有方法生成的树是否等价
func verifyAllMethods() {fmt.Println("\n========== 验证所有方法的等价性 ==========")nums := []int{-10, -3, 0, 5, 9}head := createList(nums)methods := []struct {name stringfn   func(*ListNode) *TreeNode}{{"快慢指针", sortedListToBST},{"转数组", sortedListToBST2},{"中序遍历", sortedListToBST3},{"计算长度", sortedListToBST4},}fmt.Print("输入: ")printList(head)fmt.Println()for _, method := range methods {h := createList(nums)root := method.fn(h)inorder := inorderTraversal(root)fmt.Printf("\n%s:\n", method.name)fmt.Printf("  中序遍历: %v\n", inorder)fmt.Printf("  是否平衡: %v\n", isBalanced(root))fmt.Printf("  是否BST: %v\n", isValidBST(root))fmt.Printf("  树高度: %d\n", getHeight(root))}
}func main() {// 测试用例1:示例1testCase("测试用例1:基本情况", []int{-10, -3, 0, 5, 9})// 测试用例2:空链表testCase("测试用例2:空链表", []int{})// 测试用例3:单个元素testCase("测试用例3:单个元素", []int{1})// 测试用例4:两个元素testCase("测试用例4:两个元素", []int{1, 3})// 测试用例5:奇数个元素testCase("测试用例5:奇数个元素", []int{1, 2, 3, 4, 5, 6, 7})// 测试用例6:偶数个元素testCase("测试用例6:偶数个元素", []int{1, 2, 3, 4, 5, 6})// 测试用例7:连续数字testCase("测试用例7:连续数字", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})// 扩展功能测试compareWithArray()demonstrateFindMiddle()verifyAllMethods()
}
http://www.dtcms.com/a/605739.html

相关文章:

  • Verilog 利用伪随机,时序,按键消抖等,实现一个(打地鼠)游戏
  • 【音视频】均衡器(Equalizer)技术详解
  • win11安装mysql社区版数据库
  • 菏泽定制网站建设推广花艺企业网站建设规划
  • 哪些网站可以做推广婚庆公司网站源码
  • LVS负载均衡群集(一) -- NAT模式
  • 【ZeroRnge WebRTC】RFC 8445:ICE 协议规范(中文整理与译注)
  • librtp 实现详解:仓颉语言中的 RTP和RTCP 协议库开发实践
  • Android http网络请求的那些事儿
  • 两台 centos 7.9 部署 pbs version 18.1.4 集群
  • 【动手学深度学习】8.1. 序列模型
  • 【AI软件开发】从文献管理到知识编织:构建AI驱动的学术研究工作流
  • 网站上面图片上传尺寸建设部二级结构工程师注销网站
  • PostIn从初级到进阶(3) - 如何对接口快速设计并管理接口文档
  • 按键精灵安卓/ios脚本开发辅助工具:yolo转换教程
  • 人工智能驱动下的OCR API技术演进与实践应用
  • 昆明网站建设介绍湛江专业雷剧全集
  • 网站到期时间营销型网站服务公司
  • 常用设计模式:工厂方法模式
  • 视频矩阵哪个品牌好?2025 视频矩阵品牌标杆出炉
  • MongoDB 分片
  • 网站访客qq获取苏州建网站公司
  • Vue 3与 Vue 2响应式的区别
  • 自主建站平台怎样在百度建网站
  • 开源白板工具(SaaS),一体化白板,包含思维导图、流程图、自由画等
  • 九、InnoDB引擎-MVCC
  • Cesium 性能优化:从常识到深入实践
  • 购物网站的排版番禺品牌型网站建设
  • 想学习网站建设网络公司起名大全最新
  • claude 国内注册方法(2025 年 11 月更新)