1、解题思路
- 问题分析:每个链表已经是升序排列的。我们需要将这些链表合并成一个整体升序的链表。
- 方法选择:分治法:将 k 个链表两两合并,逐步减少链表数量,直到合并成一个链表。优先队列(最小堆):将所有链表的头节点放入最小堆,每次取出最小的节点,并将其下一个节点加入堆中,直到堆为空。
- 复杂度分析:分治法:每次合并两个链表的时间为 O(n),共进行 log k 次合并,总时间为 O(n log k)。优先队列:每次插入和取出操作的时间为 O(log k),共进行 n 次操作,总时间为 O(n log k)。
- 选择实现:这里选择 优先队列 的方法,因为代码更简洁且易于理解。
2、代码实现
C++
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <queue>
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param lists ListNode类vector
* @return ListNode类
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
// write code here
// 定义优先队列的比较函数,按节点值升序排列
auto cmp = [](ListNode * a, ListNode * b) {
return a->val > b->val; // 小顶堆,值小的优先级高
};
// 创建优先队列,存储链表节点指针
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp);
// 将每个链表的头节点加入优先队列
for (ListNode* list : lists) {
if (list != nullptr) { // 确保链表非空
pq.push(list); // 将非空链表的头节点加入队列
}
}
// 创建虚拟头节点,用于简化链表操作
ListNode dummy(0);
ListNode* tail = &dummy; // 尾指针用于连接节点
// 循环处理优先队列,直到队列为空
while (!pq.empty()) {
ListNode* node = pq.top(); // 取出当前最小节点
pq.pop(); // 移除已取出的节点
tail->next = node; // 将最小节点连接到结果链表
tail = tail->next; // 移动尾指针到新连接的节点
// 如果当前节点还有后续节点,将后续节点加入队列
if (node->next != nullptr) {
pq.push(node->next);
}
}
// 返回合并后的链表头节点(跳过虚拟头节点)
return dummy.next;
}
};
Java
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param lists ListNode类ArrayList
* @return ListNode类
*/
public ListNode mergeKLists (ArrayList<ListNode> lists) {
// write code here
// 创建优先队列,按节点值升序排列
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
// 将每个链表的头节点加入优先队列
for (ListNode list : lists) {
if (list != null) { // 确保链表非空
pq.offer(list); // 将非空链表的头节点加入队列
}
}
// 创建虚拟头节点,用于简化链表操作
ListNode dummy = new ListNode(0);
ListNode tail = dummy; // 尾指针用于连接节点
// 循环处理优先队列,直到队列为空
while (!pq.isEmpty()) {
ListNode node = pq.poll(); // 取出当前最小节点
tail.next = node; // 将最小节点连接到结果链表
tail = tail.next; // 移动尾指针到新连接的节点
// 如果当前节点还有后续节点,将后续节点加入队列
if (node.next != null) {
pq.offer(node.next);
}
}
// 返回合并后的链表头节点(跳过虚拟头节点)
return dummy.next;
}
}
Python
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param lists ListNode类一维数组
# @return ListNode类
#
import heapq # 使用堆模块实现最小堆
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
# write code here
# 创建最小堆,存储三元组(节点值, 链表索引, 节点)
# 使用链表索引作为第二比较项,避免节点值相同时比较节点
min_heap = []
for idx, node in enumerate(lists):
if node: # 确保链表非空
heapq.heappush(min_heap, (node.val, idx, node))
# 创建虚拟头节点,用于简化链表操作
dummy = ListNode(0)
tail = dummy # 尾指针用于连接节点
# 循环处理最小堆,直到堆为空
while min_heap:
val, idx, node = heapq.heappop(min_heap) # 取出当前最小节点
tail.next = node # 将最小节点连接到结果链表
tail = tail.next # 移动尾指针到新连接的节点
# 如果当前节点还有后续节点,将后续节点加入堆
if node.next:
heapq.heappush(min_heap, (node.next.val, idx, node.next))
# 返回合并后的链表头节点(跳过虚拟头节点)
return dummy.next
3、复杂度分析
- 时间复杂度:每个节点被插入和取出堆一次,每次操作时间为 O(log k),总时间为 O(n log k),满足题目要求。
- 空间复杂度:堆的大小最多为 k,因此空间复杂度为 O(k)。