合并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
解答
方法一:最小堆(优先队列)
使用最小堆维护 k 个链表的当前最小节点,每次取出最小值加入结果链表。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
class MinHeap {
constructor() {
this.heap = [];
}
push(node) {
this.heap.push(node);
this.bubbleUp(this.heap.length - 1);
}
pop() {
if (this.heap.length === 0) return null;
const min = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this.bubbleDown(0);
}
return min;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
if (this.heap[index].val >= this.heap[parentIndex].val) break;
[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
index = parentIndex;
}
}
bubbleDown(index) {
while (true) {
let minIndex = index;
const leftChild = 2 * index + 1;
const rightChild = 2 * index + 2;
if (leftChild < this.heap.length && this.heap[leftChild].val < this.heap[minIndex].val) {
minIndex = leftChild;
}
if (rightChild < this.heap.length && this.heap[rightChild].val < this.heap[minIndex].val) {
minIndex = rightChild;
}
if (minIndex === index) break;
[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];
index = minIndex;
}
}
isEmpty() {
return this.heap.length === 0;
}
}
var mergeKLists = function(lists) {
const heap = new MinHeap();
// 将所有链表的头节点加入堆
for (const head of lists) {
if (head) heap.push(head);
}
const dummy = new ListNode(0);
let current = dummy;
// 不断取出最小节点
while (!heap.isEmpty()) {
const minNode = heap.pop();
current.next = minNode;
current = current.next;
// 如果该节点有后续节点,加入堆
if (minNode.next) {
heap.push(minNode.next);
}
}
return dummy.next;
};
方法二:分治合并
两两合并链表,类似归并排序的思想。
var mergeKLists = function(lists) {
if (lists.length === 0) return null;
// 两两合并,直到只剩一个链表
while (lists.length > 1) {
const merged = [];
for (let i = 0; i < lists.length; i += 2) {
const l1 = lists[i];
const l2 = i + 1 < lists.length ? lists[i + 1] : null;
merged.push(mergeTwoLists(l1, l2));
}
lists = merged;
}
return lists[0];
};
function mergeTwoLists(l1, l2) {
const dummy = new ListNode(0);
let current = dummy;
while (l1 && l2) {
if (l1.val < l2.val) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
current.next = l1 || l2;
return dummy.next;
}
关键点
- 最小堆方法时间复杂度 O(N log k),N 为所有节点总数,k 为链表数量
- 分治方法时间复杂度 O(N log k),空间复杂度更优
- 最小堆需要维护 k 个元素,每次插入和删除操作为 O(log k)
- 分治方法每轮合并链表数量减半,共需 log k 轮
- 使用虚拟头节点简化链表操作
目录