23. 合并 K 个升序链表
- 题目
- 算法 = k 个指针分别指向 k 条链表 & 题目特征 = 合并俩个链表的升级版
- 算法 = 最小堆 & 题目特征 = 快速获取 k 个节点中的最小节点
题目
题目链接:https://leetcode.cn/problems/merge-k-sorted-lists/
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
算法 = k 个指针分别指向 k 条链表 & 题目特征 = 合并俩个链表的升级版
合并 k 个有序链表的逻辑类似合并两个有序链表。
难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上?
维护一个最小节点的指针和对应的链表索引,依次选择最小的节点进行合并。
通过不断更新最小节点和链表指针,最终完成K个有序链表的合并。
假设有三个有序链表:
List 1: 1 -> 4 -> 5
List 2: 1 -> 3 -> 4
List 3: 2 -> 6
初始状态下,每个链表的指针如下:
List 1: 1 -> 4 -> 5
^
List 2: 1 -> 3 -> 4
^
List 3: 2 -> 6
^
首先,minNode和minPointer初始化为null和-1。
在第一次循环中,遍历三个链表,比较当前节点的值。minNode为null,所以将List 1的第一个节点1赋值给minNode,并将minPointer设为0(表示最小节点在List 1中)。
接下来,将minNode(即1)添加到结果链表中,然后更新List 1的指针,使其指向下一个节点。
结果链表:1
List 1: 4 -> 5
^
List 2: 1 -> 3 -> 4
^
List 3: 2 -> 6
^
在第二次循环中,再次遍历三个链表。minNode为1,minPointer为0(表示最小节点在List 1中)。
将minNode(即1)添加到结果链表中,然后更新List 2的指针,使其指向下一个节点。
结果链表:1 -> 1
List 1: 4 -> 5
^
List 2: 3 -> 4
^
List 3: 2 -> 6
^
在第三次循环中,再次遍历三个链表。minNode为1,minPointer为1(表示最小节点在List 2中)。
将minNode(即1)添加到结果链表中,然后更新List 1的指针,使其指向下一个节点。
结果链表:1 -> 1 -> 2
List 1: 4 -> 5
^
List 2: 3 -> 4
^
List 3: 2 -> 6
^
继续循环直到所有链表遍历完毕。最终得到的合并后的有序链表为:1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6。
完整代码:
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int k = lists.size(); // 通过传入的 vector lists,获取链表的数量k
ListNode* dummyHead = new ListNode(0); // 创建一个虚拟头结点dummyHead
ListNode* tail = dummyHead; // 将尾指针tail指向dummyHead
while (true) { // 进入一个无限循环,直到跳出循环为止
ListNode* minNode = nullptr; // 定义minNode指针,用于记录当前最小节点
int minPointer = -1; // 定义minPointer变量,用于记录对应的链表索引
for (int i = 0; i < k; i++) { // 遍历k个链表,判断当前链表指针lists[i]是否为空
if (lists[i] == nullptr) // 如果为空
continue; // 则继续遍历下一个链表
if (minNode == nullptr || lists[i]->val < minNode->val) { // 如果minNode为空 或者 lists[i]的值小于minNode的值
minNode = lists[i]; // 则更新minNode为lists[i]
minPointer = i; // minPointer为i
}
}
if (minPointer == -1) // 如果minPointer仍然为-1,说明所有链表已经遍历完毕
break; // 跳出循环
tail->next = minNode; // 将minNode连接到结果链表的尾部,即tail->next = minNode
tail = tail->next; // 并更新tail指针为minNode
lists[minPointer] = lists[minPointer]->next; // 更新lists[minPointer]为下一个节点
}
return dummyHead->next; // 返回合并后的链表的头节点
}
};
算法 = 最小堆 & 题目特征 = 快速获取 k 个节点中的最小节点
合并 k 个有序链表的逻辑类似合并两个有序链表。
难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上?
把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点。
对比前一种方法。
- 减少比较次数:在每次循环中,只需从优先级队列中取出最小节点,而不是遍历K个链表进行比较。
- 无需每次更新最小节点的指针:在之前的方法中,通过遍历K个链表并比较节点值,确定最小节点后需要更新该链表的指针。而在最小堆的方法中,只需将最小节点的下一个节点加入优先级队列,无需显式更新指针。
这样可以减少比较的次数,提高效率。
class Solution {
public:
struct compare {
bool operator()(const ListNode* a, const ListNode* b) {
return a->val > b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.size() == 0) return nullptr;
// 虚拟头结点
ListNode* dummy = new ListNode(-1);
ListNode* p = dummy;
// 优先级队列,最小堆
priority_queue<ListNode*, vector<ListNode*>, compare> pq;
// 将 k 个链表的头结点加入最小堆
for (ListNode* head : lists) {
if (head != nullptr)
pq.push(head);
}
while (!pq.empty()) {
// 获取最小节点,接到结果链表中
ListNode* node = pq.top();
pq.pop();
p->next = node;
if (node->next != nullptr)
pq.push(node->next);
// p 指针不断前进
p = p->next;
}
return dummy->next;
}
};