23. 合并 K 个升序链表(困难)
题目
给你 k 个单链表,每个链表都已经按升序排好。请把它们全部合并成一条升序链表,并返回新链表的头节点。
换句话说,就是做一次 k 路归并:每一轮从所有链表的“当前头部”里挑出最小的那个,接到结果尾部。
自造示例:lists = [[2,5,7],[1,3,4,4],[0,6]] → [0,1,2,3,4,4,5,6,7]
约束:链表数 0 ~ 104;每个链表长度 0 ~ 500;所有节点总数不超过 104;节点值 -104 ~ 104;每个链表已按升序排列。
思路
一句话:把 k 条有序链表的“当前头节点”放进一个最小堆,每次取堆顶接到结果尾部,再把它后面的节点补进堆。
最直白的做法:每取一个节点,就扫描 k 个链表的当前头,找出最小值。总共要取 n 个节点,每取一个扫 k 个 → 时间 O(n·k)。k 最大到 104,会太慢。
另一个“偷懒但能过”的做法:把所有节点值倒进数组,整体排序后再重建链表。时间 O(n log n),确实能通过,但它丢掉了“每个链表本来就有序”这个重要信息,还额外用了 O(n) 空间存值。
关键观察:全局最小值一定藏在这 k 个“当前头”里——因为每条链表都是升序,如果某条链的头没被选中,它后面的节点只会更大,不可能抢先成为全局最小。所以候选节点永远只有 k 个。
于是问题就变成:在 k 个候选里反复“取最小值 + 补一个新候选”。这正是最小堆的标准用途:取出 O(log k)、插入 O(log k),整体 O(n log k)。
解法
核心步骤:
- 建一个最小堆,把每条链表非空的头节点入堆(只入头,不入整条)。
- 用一个哨兵节点
dummy让“头节点也是普通节点”统一处理。 - 循环:弹出堆顶,接到
tail;如果它后面还有节点,就把next入堆。 - 堆为空时结束,返回
dummy.next。
以 lists = [[2,5,7],[1,3,4,4],[0,6]] 为例,过程如下:
| 已取个数 | 堆中候选(节点值) | 本次取出 | 补入堆 | 结果尾部 |
|---|---|---|---|---|
| 初始 | 0,1,2 | — | — | — |
| 1 | 0,1,2 | 0 | 6 | 0 |
| 2 | 1,2,6 | 1 | 3 | 0→1 |
| 3 | 2,3,6 | 2 | 5 | 0→1→2 |
| 4 | 3,5,6 | 3 | 4 | 0→1→2→3 |
| 5 | 4,5,6 | 4 | 4 | 0→1→2→3→4 |
| 6 | 4,5,6 | 4 | — | 0→1→2→3→4→4 |
| 7 | 5,6 | 5 | 7 | 0→1→2→3→4→4→5 |
| 8 | 6,7 | 6 | — | 0→1→2→3→4→4→5→6 |
| 9 | 7 | 7 | — | 0→1→2→3→4→4→5→6→7 |
堆里始终只保留“每条链下一个未取节点”,最多 k 个。
下图把上面 4 个步骤串成流程,并逐步演示:


C++
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto cmp = [](ListNode* a, ListNode* b) {
return a->val > b->val; // 小顶堆:值小的排前面
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp); for (ListNode* head : lists) { // 每条链只放一个头节点
if (head != nullptr) {
pq.push(head);
}
} ListNode dummy(0);
ListNode* tail = &dummy;本篇为付费内容
您已阅读试读部分,购买后即可解锁全文。
⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。
还没有评论,来说两句?