题目描述
看到这个题目会想起之前做过的合并2个升序链表。在那个题目中,由于两个链表都已经是升序的,可以将两个链表的元素进行逐个比较并添加到答案链表中。但是在本题中,每次循环都需要在k个链表的当前元素中找出最小的,而且需要在所有k个链表都遍历完之后跳出循环,所以效率比较低。
class Solution {
public:bool checkIsEnd(vector<ListNode*>& lists) {for (int i = 0; i < lists.size(); i++) {if (lists[i] != nullptr) return false;}return true;}ListNode* mergeKLists(vector<ListNode*>& lists) {if (lists.empty()) return nullptr;ListNode* dummy = new ListNode();ListNode* p = dummy;int k = lists.size();while (true) {if (checkIsEnd(lists)) break;ListNode* minNode = nullptr;int minNodeIdx = -1;for (int i = 0; i < k; i++) {if (lists[i] == nullptr) continue;if (minNode == nullptr || minNode->val > lists[i]->val) {minNode = lists[i];minNodeIdx = i;}}p->next = minNode;p = p->next;lists[minNodeIdx] = lists[minNodeIdx]->next;}return dummy->next;}
};
这种方法中影响时间效率的主要在于:每次循环都需要首先判断指针是否已经到达链表结尾,以及每次比较k个元素的最小值都需要遍历k个元素。这里我的一个想法是优化第一个点:使用一个bitmap存储第i
个链表是否已经达到结尾,这样直接进行位运算比通过循环进行判断效率高得多,但是由于需要k位长的bit串,而题目中k的设置可以非常大,超过计算机的极限,因此这个方法不太合适。
能否使用优先队列(堆)解决k个元素的最小值的问题?可以构建一个优先队列,内部维护k个当前链表结点,每次pop出最小的结点(相当于第一种方法中将最小结点加入答案链表中),再把后面一个结点加入优先队列中(相当于第一种方法中将指针向前移动一个结点),这样循环,直到优先队列中没有剩余元素为止,说明所有k个链表中的所有元素已经比较完毕了。
class Solution {
public:struct Node {ListNode* ptr;bool operator<(const Node& rhs) const {return ptr->val > rhs.ptr->val;}};priority_queue<Node> q;ListNode* mergeKLists(vector<ListNode*>& lists) {for (ListNode* node : lists) {if (node != nullptr) {q.push({node});}}ListNode *head = new ListNode(), *tail = head;while (!q.empty()) {Node node = q.top();q.pop();tail->next = node.ptr;tail = tail->next;if (node.ptr->next) {q.push({node.ptr->next});}}return head->next;}
};
其实从合并2个有序链表的想法出发,可以采用类似分治、合并的方法,比如:顺序合并,用一个链表ans
来维护已经合并的链表,然后循环k
次,第i
次合并第i
个链表,最终把所有链表合并进去;分治合并,将k
个链表分组配对,并将每一对中的链表进行两两合并,如此进行多轮合并直到剩下一个链表作为答案。这两种方法也是官方题解给出的前两种解法,想法是将问题归约为合并2个有序链表的方法,并直接利用。