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

【华为机试】23. 合并 K 个升序链表

文章目录

  • 23. 合并 K 个升序链表
  • 描述
    • 示例 1
    • 示例 2
    • 示例 3
    • 提示
    • 解题思路
      • 算法分析
      • 问题本质分析
      • 分治算法详解
      • 两个链表合并过程
      • 分治树结构
      • 优先队列解法
      • 各种解法对比
      • 算法流程图
      • 代码实现思路
      • 时间复杂度分析
      • 空间复杂度分析
      • 关键优化点
      • 边界情况处理
      • 实际应用场景
      • 算法扩展
      • 性能优化技巧
      • 测试用例设计
    • 完整题解代码

23. 合并 K 个升序链表

描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2

输入:lists = []
输出:[]

示例 3

输入:lists = [[]]
输出:[]

提示

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] 按 升序 排列
  • lists[i].length 的总和不超过 10^4

解题思路

算法分析

这道题是链表合并分治算法的经典应用。主要解法包括:

  1. 逐一合并法:依次合并两个链表,简单但效率低
  2. 分治合并法:类似归并排序,递归分治合并
  3. 优先队列法:使用最小堆维护各链表头节点
  4. 数组排序法:收集所有节点后排序重建链表

问题本质分析

合并K个升序链表
多路归并问题
两两合并策略
分治策略
堆优化策略
逐一合并
分治合并
优先队列
时间复杂度O_kn
时间复杂度O_nlogk
时间复杂度O_nlogk

分治算法详解

空数组
单个链表
多个链表
K个链表分治合并
链表数量判断
返回null
直接返回
分治递归
左半部分合并
右半部分合并
递归调用
递归调用
合并两个有序链表
返回合并结果

两个链表合并过程

list1较小
list2较小
合并两个有序链表
创建虚拟头节点
双指针遍历
比较节点值
连接list1节点
连接list2节点
移动list1指针
移动list2指针
是否遍历完成
连接剩余节点
返回结果链表

分治树结构

合并8个链表
左4个
右4个
左2个
右2个
左2个
右2个
链表1
链表2
链表3
链表4
链表5
链表6
链表7
链表8

优先队列解法

非空
优先队列解法
初始化最小堆
所有链表头节点入堆
堆是否为空
合并完成
取出最小节点
连接到结果链表
节点有后继
后继节点入堆
继续下一轮

各种解法对比

解法对比
逐一合并
分治合并
优先队列
数组排序
时间O_kn空间O常数
时间O_nlogk空间O_logk
时间O_nlogk空间O_k
时间O_nlogn空间O_n
实现简单效率低
经典分治推荐
堆优化稳定
简单粗暴

算法流程图

分治法
优先队列
逐一合并
输入K个升序链表
选择算法
分治合并
堆合并
顺序合并
递归分割
两两合并
递归向上合并
返回最终结果
建立最小堆
依次取最小节点
构建结果链表
依次合并相邻链表
重复直到剩一个

代码实现思路

  1. 链表节点定义

    • 标准单链表节点结构
    • 包含值和下一个节点指针
  2. 分治合并实现

    • 递归分割链表数组
    • 合并两个有序链表的经典算法
    • 时间复杂度最优
  3. 优先队列实现

    • 使用堆维护所有链表的当前最小节点
    • 每次取出最小节点并添加其后继
    • 适合K很大的情况
  4. 边界处理

    • 空数组、单链表、空链表的处理
    • 链表长度不等的情况

时间复杂度分析

  • 逐一合并法:O_kn,k为链表数量,n为总节点数
  • 分治合并法:O_nlogk,最优解法
  • 优先队列法:O_nlogk,稳定高效
  • 数组排序法:O_nlogn,简单但空间消耗大

空间复杂度分析

  • 逐一合并法:O常数,原地操作
  • 分治合并法:O_logk,递归栈深度
  • 优先队列法:O_k,堆存储空间
  • 数组排序法:O_n,额外数组空间

关键优化点

  1. 分治策略:将O_kn优化为O_nlogk
  2. 虚拟头节点:简化链表合并逻辑
  3. 递归优化:合理的递归边界处理
  4. 内存管理:复用原有节点,避免额外分配

边界情况处理

  1. 空数组:直接返回null
  2. 单个链表:直接返回该链表
  3. 包含空链表:跳过空链表处理
  4. 所有链表为空:返回null

实际应用场景

  1. 外部排序:合并多个已排序的文件
  2. 分布式系统:合并多个节点的排序结果
  3. 数据库:多路归并排序的实现
  4. 搜索引擎:合并多个索引的结果

算法扩展

算法扩展
归并排序
外部排序
多路归并
分布式归并
分治思想应用
大数据处理
K路归并算法
分布式计算
算法模式复用

性能优化技巧

性能优化
算法选择
数据结构优化
内存优化
并行化
根据K值选择算法
堆vs分治权衡
原地操作减少分配
多线程分治
实际性能提升

测试用例设计

测试用例
基础功能
边界情况
性能测试
正常合并
重复元素
空数组空链表
单链表
大规模数据
极端K值
验证正确性
验证性能

这个问题的关键在于理解分治算法的优势掌握链表合并的基本操作,通过递归分治将复杂的多路归并问题转化为简单的两路归并问题。

完整题解代码

package mainimport ("container/heap""fmt""sort""time"
)// ListNode 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// 方法一:分治合并法(推荐)
// 时间复杂度:O(n log k),空间复杂度:O(log k)
func mergeKLists(lists []*ListNode) *ListNode {if len(lists) == 0 {return nil}if len(lists) == 1 {return lists[0]}return divide(lists, 0, len(lists)-1)
}// 分治递归函数
func divide(lists []*ListNode, left, right int) *ListNode {if left == right {return lists[left]}mid := left + (right-left)/2leftList := divide(lists, left, mid)rightList := divide(lists, mid+1, right)return mergeTwoLists(leftList, rightList)
}// 合并两个有序链表
func mergeTwoLists(l1, l2 *ListNode) *ListNode {dummy := &ListNode{}current := dummyfor l1 != nil && l2 != nil {if l1.Val <= l2.Val {current.Next = l1l1 = l1.Next} else {current.Next = l2l2 = l2.Next}current = current.Next}// 连接剩余节点if l1 != nil {current.Next = l1}if l2 != nil {current.Next = l2}return dummy.Next
}// 方法二:优先队列法(最小堆)
// 时间复杂度:O(n log k),空间复杂度:O(k)
type NodeHeap []*ListNodefunc (h NodeHeap) Len() int           { return len(h) }
func (h NodeHeap) Less(i, j int) bool { return h[i].Val < h[j].Val }
func (h NodeHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }func (h *NodeHeap) Push(x interface{}) {*h = append(*h, x.(*ListNode))
}func (h *NodeHeap) Pop() interface{} {old := *hn := len(old)x := old[n-1]*h = old[0 : n-1]return x
}func mergeKListsHeap(lists []*ListNode) *ListNode {if len(lists) == 0 {return nil}// 初始化最小堆h := &NodeHeap{}heap.Init(h)// 将所有非空链表的头节点加入堆for _, list := range lists {if list != nil {heap.Push(h, list)}}dummy := &ListNode{}current := dummy// 依次取出最小节点for h.Len() > 0 {node := heap.Pop(h).(*ListNode)current.Next = nodecurrent = current.Next// 如果该节点有后继,将后继加入堆if node.Next != nil {heap.Push(h, node.Next)}}return dummy.Next
}// 方法三:逐一合并法
// 时间复杂度:O(k*n),空间复杂度:O(1)
func mergeKListsSequential(lists []*ListNode) *ListNode {if len(lists) == 0 {return nil}result := lists[0]for i := 1; i < len(lists); i++ {result = mergeTwoLists(result, lists[i])}return result
}// 方法四:数组排序法
// 时间复杂度:O(n log n),空间复杂度:O(n)
func mergeKListsArray(lists []*ListNode) *ListNode {if len(lists) == 0 {return nil}// 收集所有节点值var values []intfor _, list := range lists {current := listfor current != nil {values = append(values, current.Val)current = current.Next}}if len(values) == 0 {return nil}// 排序sort.Ints(values)// 重建链表dummy := &ListNode{}current := dummyfor _, val := range values {current.Next = &ListNode{Val: val}current = current.Next}return dummy.Next
}// 辅助函数:创建链表
func createList(values []int) *ListNode {if len(values) == 0 {return nil}dummy := &ListNode{}current := dummyfor _, val := range values {current.Next = &ListNode{Val: val}current = current.Next}return dummy.Next
}// 辅助函数:打印链表
func printList(head *ListNode) {current := headfor current != nil {fmt.Printf("%d", current.Val)if current.Next != nil {fmt.Print("->")}current = current.Next}fmt.Println()
}// 辅助函数:链表转数组(用于验证结果)
func listToArray(head *ListNode) []int {var result []intcurrent := headfor current != nil {result = append(result, current.Val)current = current.Next}return result
}// 辅助函数:数组比较
func arraysEqual(a, b []int) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// 性能测试函数
func measureTime(name string, fn func() *ListNode) *ListNode {start := time.Now()result := fn()duration := time.Since(start)fmt.Printf("%s: 耗时=%v\n", name, duration)return result
}// 运行所有测试用例
func runTests() {fmt.Println("=== 23. 合并K个升序链表 测试用例 ===")// 测试用例1:示例1fmt.Println("\n--- 测试用例1:示例1 ---")lists1 := []*ListNode{createList([]int{1, 4, 5}),createList([]int{1, 3, 4}),createList([]int{2, 6}),}fmt.Println("输入链表:")for i, list := range lists1 {fmt.Printf("链表%d: ", i+1)printList(list)}// 测试所有方法result1_1 := mergeKLists(copyLists(lists1))result1_2 := mergeKListsHeap(copyLists(lists1))result1_3 := mergeKListsSequential(copyLists(lists1))result1_4 := mergeKListsArray(copyLists(lists1))fmt.Println("结果:")fmt.Print("分治合并法: ")printList(result1_1)fmt.Print("优先队列法: ")printList(result1_2)fmt.Print("逐一合并法: ")printList(result1_3)fmt.Print("数组排序法: ")printList(result1_4)// 验证结果一致性expected1 := []int{1, 1, 2, 3, 4, 4, 5, 6}if arraysEqual(listToArray(result1_1), expected1) &&arraysEqual(listToArray(result1_2), expected1) &&arraysEqual(listToArray(result1_3), expected1) &&arraysEqual(listToArray(result1_4), expected1) {fmt.Println("✅ 所有方法结果正确且一致")} else {fmt.Println("❌ 结果不一致!")}// 测试用例2:空数组fmt.Println("\n--- 测试用例2:空数组 ---")lists2 := []*ListNode{}result2 := mergeKLists(lists2)fmt.Printf("结果: %v (预期: null)\n", result2)// 测试用例3:包含空链表fmt.Println("\n--- 测试用例3:包含空链表 ---")lists3 := []*ListNode{nil}result3 := mergeKLists(lists3)fmt.Printf("结果: %v (预期: null)\n", result3)// 测试用例4:单个链表fmt.Println("\n--- 测试用例4:单个链表 ---")lists4 := []*ListNode{createList([]int{1, 2, 3})}result4 := mergeKLists(lists4)fmt.Print("结果: ")printList(result4)// 测试用例5:重复元素fmt.Println("\n--- 测试用例5:重复元素 ---")lists5 := []*ListNode{createList([]int{1, 1, 2}),createList([]int{1, 1, 2}),}result5 := mergeKLists(lists5)fmt.Print("结果: ")printList(result5)// 性能测试fmt.Println("\n--- 性能对比测试 ---")performanceTest()
}// 复制链表数组(因为合并会修改原链表)
func copyLists(lists []*ListNode) []*ListNode {copied := make([]*ListNode, len(lists))for i, list := range lists {copied[i] = copyList(list)}return copied
}// 复制单个链表
func copyList(head *ListNode) *ListNode {if head == nil {return nil}dummy := &ListNode{}current := dummyfor head != nil {current.Next = &ListNode{Val: head.Val}current = current.Nexthead = head.Next}return dummy.Next
}// 性能对比测试
func performanceTest() {// 创建测试数据testLists := []*ListNode{createList([]int{1, 4, 7, 10, 13}),createList([]int{2, 5, 8, 11, 14}),createList([]int{3, 6, 9, 12, 15}),createList([]int{16, 17, 18, 19, 20}),createList([]int{21, 22, 23, 24, 25}),}fmt.Printf("测试数据:5个链表,每个链表5个元素\n")// 测试分治合并法result1 := measureTime("分治合并法", func() *ListNode {return mergeKLists(copyLists(testLists))})// 测试优先队列法result2 := measureTime("优先队列法", func() *ListNode {return mergeKListsHeap(copyLists(testLists))})// 测试逐一合并法result3 := measureTime("逐一合并法", func() *ListNode {return mergeKListsSequential(copyLists(testLists))})// 测试数组排序法result4 := measureTime("数组排序法", func() *ListNode {return mergeKListsArray(copyLists(testLists))})// 验证结果一致性arr1 := listToArray(result1)arr2 := listToArray(result2)arr3 := listToArray(result3)arr4 := listToArray(result4)if arraysEqual(arr1, arr2) && arraysEqual(arr2, arr3) && arraysEqual(arr3, arr4) {fmt.Println("✅ 所有方法结果一致")fmt.Print("合并结果: ")printList(result1)} else {fmt.Println("❌ 结果不一致!")}
}// 展示分治过程
func demonstrateDivideConquer() {fmt.Println("\n--- 分治过程演示 ---")lists := []*ListNode{createList([]int{1, 4}),createList([]int{1, 3}),createList([]int{2, 6}),createList([]int{5, 7}),}fmt.Println("原始4个链表:")for i, list := range lists {fmt.Printf("链表%d: ", i+1)printList(list)}fmt.Println("\n分治合并过程:")fmt.Println("第1轮:合并相邻链表对")merged1 := mergeTwoLists(copyList(lists[0]), copyList(lists[1]))merged2 := mergeTwoLists(copyList(lists[2]), copyList(lists[3]))fmt.Print("合并链表1+2: ")printList(merged1)fmt.Print("合并链表3+4: ")printList(merged2)fmt.Println("\n第2轮:合并结果")final := mergeTwoLists(merged1, merged2)fmt.Print("最终结果: ")printList(final)
}func main() {runTests()demonstrateDivideConquer()
}
http://www.xdnf.cn/news/15824.html

相关文章:

  • Leetcode 06 java
  • LeetCode 121. 买卖股票的最佳时机
  • 试用SAP BTP 02:试用SAP HANA Cloud
  • 算法分析--时间复杂度
  • Hadoop小文件合并技术深度解析:HAR文件归档、存储代价与索引结构
  • Function Callingの进化路:源起篇
  • gradle关于dependency-management的使用
  • 【实证分析】会计稳健性指标分析-ACF、CScore、Basu模型(2000-2023年)
  • 贝叶斯分类器的相关理论学习
  • Qwen3-8B 的 TTFT 性能分析:16K 与 32K 输入 Prompt 的推算公式与底层原理详解
  • 乐观锁实现原理笔记
  • 【论文阅读笔记】RF-Diffusion: Radio Signal Generation via Time-Frequency Diffusion
  • 考研最高效的准备工作是什么
  • 力扣面试150(34/150)
  • 隧道无线调频广播与“群载波”全频插播技术在张石高速黑石岭隧道中的应用
  • 44.sentinel授权规则
  • 【Java多线程-----复习】
  • 04训练windows电脑低算力显卡如何部署pytorch实现GPU加速
  • 标准制修订管理系统:制造业高质量发展的关键支撑
  • 【Java学习|黑马笔记|Day18】Stream流|获取、中间方法、终结方法、收集方法
  • python 装饰器的类型提示讲解
  • 下载win10的方法
  • Hiredis 构建 Redis 命令实战指南
  • 操作系统总结
  • XSS GAME靶场
  • 网络原理——IP
  • 深度神经网络原理学习记录
  • 微服务雪崩防护最佳实践之sentinel
  • Django ORM系统
  • SearchService 该类只运行在数据节点