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

文章目录

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 TD A[输入: -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:#e1f5ff style E fill:#bbdefb style G fill:#90caf9 style J fill:#c8e6c9 style 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. 递归构建
    • 创建根节点(中点值)
    • 递归处理左半链表 → 左子树
    • 递归处理右半链表 → 右子树

代码实现

go 复制代码
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 = slow
        slow = slow.Next
        fast = 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 LR A[链表: -10→-3→0→5→9] --> B[遍历转数组] B --> C[数组: -10,-3,0,5,9] C --> D[108题方法构建BST] D --> E[返回根节点] style A fill:#e1f5ff style C fill:#bbdefb style 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. 快慢指针找中点

    go 复制代码
    slow, fast := head, head
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = 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变平衡

完整题解代码

go 复制代码
package main

import (
	"fmt"
	"math"
)

// ListNode 链表节点定义
type ListNode struct {
	Val  int
	Next *ListNode
}

// TreeNode 二叉树节点定义
type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *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, head
	var prev *ListNode

	for fast != nil && fast.Next != nil {
		prev = slow
		slow = slow.Next
		fast = fast.Next.Next
	}

	// 断开链表:prev.Next = nil
	if 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 := head
	for curr != nil {
		nums = append(nums, curr.Val)
		curr = curr.Next
	}

	// 使用108题的方法构建BST
	return arrayToBST(nums, 0, len(nums)-1)
}

func arrayToBST(nums []int, left, right int) *TreeNode {
	if left > right {
		return nil
	}

	mid := left + (right-left)/2
	root := &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 := 0
	curr := head
	for 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 = leftTree
	root.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 := 0
	for 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 := head
	for 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 := head

	for 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)
		}
	}

	// 移除末尾的 nil
	for 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, head
		var prev *ListNode

		for fast != nil && fast.Next != nil {
			prev = slow
			slow = slow.Next
			fast = 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 string
		fn   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:示例1
	testCase("测试用例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()
}
相关推荐
ShiMetaPi14 分钟前
SAM(通用图像分割基础模型)丨基于BM1684X模型部署指南
人工智能·算法·ai·开源·bm1684x·算力盒子
前端小白在前进17 分钟前
力扣刷题:无重复字符的最长子串
算法·leetcode·职场和发展
小小的橙菜吖!18 分钟前
联合体的学习
学习·算法
NPE~28 分钟前
面试高频——分布式事务详解
分布式·面试·职场和发展·程序员·事务·分布式事务
Xing_ke30939 分钟前
3D点云分割与检测(后续更新)
算法·3d点云
4311媒体网40 分钟前
C语言实现简单的二分查找算法
c语言·开发语言·算法
持续学习的程序员+141 分钟前
π RL(piRL)算法支持用强化学习方法训练π 0/π 0.5(pi0/pi0.5)
算法
csuzhucong1 小时前
112魔方、113魔方、114魔方
算法
无限进步_1 小时前
C语言实现贪吃蛇游戏详解
c语言·开发语言·数据结构·c++·后端·算法·游戏
Element_南笙1 小时前
吴恩达新课程:Agentic AI(笔记11)
大数据·人工智能·笔记·算法·机器学习