【LeetCode 热题100】23:合并 K 个升序链表(详细解析)(Go语言版)

🚀 LeetCode 热题 23:合并 K 个升序链表(详细解析)

📌 题目描述

LeetCode 23. Merge k Sorted Lists

给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表。


🎯 示例 1:

lua 复制代码
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]

🎯 示例 2:

ini 复制代码
输入:lists = []
输出:[]

🎯 示例 3:

lua 复制代码
输入:lists = [[]]
输出:[]

💡 解题思路一:优先队列(最小堆)

使用最小堆(Priority Queue)维护 k 个链表的头节点,每次取堆顶最小值节点,接入结果链表。

✅ 步骤解析

  1. 初始化最小堆,将每个非空链表的头节点入堆。
  2. 不断从堆中弹出最小值节点,将其加入结果链表。
  3. 如果弹出的节点有 next 节点,则将 next 节点入堆。
  4. 最后返回结果链表头部。

💻 Go 实现(使用 heap 接口)

go 复制代码
type ListNode struct {
    Val  int
    Next *ListNode
}

type NodeHeap []*ListNode

func (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 := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func mergeKLists(lists []*ListNode) *ListNode {
    h := &NodeHeap{}
    heap.Init(h)

    for _, node := range lists {
        if node != nil {
            heap.Push(h, node)
        }
    }

    dummy := &ListNode{}
    curr := dummy

    for h.Len() > 0 {
        node := heap.Pop(h).(*ListNode)
        curr.Next = node
        curr = curr.Next
        if node.Next != nil {
            heap.Push(h, node.Next)
        }
    }

    return dummy.Next
}

💡 解题思路二:分治合并(归并)

  • 将 k 个链表两两合并,逐步缩小范围。
  • 类似归并排序的思想。

✅ 步骤解析

  1. 每次将链表对半拆分,递归合并每一对链表。
  2. 使用合并两个有序链表的经典方法。

💻 Go 实现

go 复制代码
func mergeKLists(lists []*ListNode) *ListNode {
    if len(lists) == 0 {
        return nil
    }
    return mergeRange(lists, 0, len(lists)-1)
}

func mergeRange(lists []*ListNode, l, r int) *ListNode {
    if l == r {
        return lists[l]
    }
    mid := (l + r) / 2
    left := mergeRange(lists, l, mid)
    right := mergeRange(lists, mid+1, r)
    return mergeTwoLists(left, right)
}

func mergeTwoLists(l1, l2 *ListNode) *ListNode {
    dummy := &ListNode{}
    curr := dummy

    for l1 != nil && l2 != nil {
        if l1.Val < l2.Val {
            curr.Next = l1
            l1 = l1.Next
        } else {
            curr.Next = l2
            l2 = l2.Next
        }
        curr = curr.Next
    }

    if l1 != nil {
        curr.Next = l1
    } else {
        curr.Next = l2
    }

    return dummy.Next
}

📊 解法对比 & 复杂度分析

解法 时间复杂度 空间复杂度 特点说明
✅ 最小堆 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n log ⁡ k ) O(n \log k) </math>O(nlogk) <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( k ) O(k) </math>O(k) 快速选择当前最小节点
✅ 分治合并 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n log ⁡ k ) O(n \log k) </math>O(nlogk) <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( log ⁡ k ) O(\log k) </math>O(logk) 简洁高效,适合面试常考套路
  • 其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是所有链表节点总数, <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 是链表数量。

🧠 总结

  • 🔧 如果要求效率,最小堆优先,适合在线合并场景。
  • 📚 如果偏向代码整洁,可选择分治合并,便于拓展与理解。
  • 💡 重点掌握:链表合并逻辑 + 堆操作 + 分治模板。

✅ 如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、关注 💻,我会持续更新更多高质量 LeetCode 热题详解!🚀📌🎯


相关推荐
历程里程碑1 分钟前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法
Sheep Shaun3 分钟前
如何让一个进程诞生、工作、终止并等待回收?——探索Linux进程控制与Shell的诞生
linux·服务器·数据结构·c++·算法·shell·进程控制
Pluchon4 分钟前
硅基计划4.0 简单模拟实现AVL树&红黑树
java·数据结构·算法
生锈的键盘13 分钟前
推荐算法实践:交叉特征的理解
算法
乌萨奇也要立志学C++26 分钟前
【洛谷】BFS 求解最短路:从马的遍历到迷宫问题的实战解析
算法·宽度优先
老鼠只爱大米35 分钟前
LeetCode经典算法面试题 #46:全排列(回溯、交换、剪枝等五种实现方案详细解析)
算法·leetcode·剪枝·回溯·全排列·stj算法
却尘37 分钟前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
Dovis(誓平步青云)1 小时前
《滑动窗口算法:从 “暴力遍历” 到 “线性高效” 的思维跃迁》
运维·服务器·数据库·算法
ん贤1 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
_OP_CHEN1 小时前
【算法基础篇】(五十七)线性代数之矩阵乘法从入门到实战:手撕模板 + 真题详解
线性代数·算法·矩阵·蓝桥杯·c/c++·矩阵乘法·acm/icpc