给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 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
老样子,先贴我的图,可以说是又快又好,不过这里原题给的是子链表有序(常规写法也能写,不难),如果子链表无序,那各位读者应该是焦头烂额+汗流浃背了
这个题,其实看完我上一篇分享可以立马写出来了,链表排序?看完你也能手撕
思路还是一样,先转换成数组存放,再排序,再存回链表中(思路很简单)
class Solution { public: ListNode* mergeKLists(vector<ListNode*>& lists) { vector<int> s; // 存储所有链表节点的值的容器 // 遍历所有链表,将节点的值依次放入容器中 for (int i = 0; i < lists.size(); i++) { ListNode* cur = lists[i]; // 当前链表的头节点 while (cur != nullptr) { s.push_back(cur->val); // 将节点值加入容器 cur = cur->next; // 指针移动到下一个节点 } } sort(s.begin(),s.end()); // 将容器中的值进行排序(升序) ListNode *tt = new ListNode(-1); // 创建一个哑节点 tt,用于构建结果链表 ListNode *res = tt; // 使用 res 保存结果链表的头节点 int i = 0; // 用于遍历排序后的容器 s // 遍历列表 for (int j = 0; j < lists.size(); j++) { ListNode* cur1 = lists[j]; // 当前链表的头节点 while(cur1 != nullptr) { cur1->val = s[i++]; // 将排序后的值赋给当前节点 tt->next = cur1; // 连接当前节点到结果链表 tt = cur1; // tt 指针移动到当前节点 cur1 = cur1->next; // cur1 指针移动到下一个节点 } } return res->next; // 返回结果链表的头节点 } };
以下是LeetCode官方的各种题解,为大家添加上了注释
方法一:顺序合并
我们可以想到一种最朴素的方法:用一个变量 ans 来维护以及合并的链表,第 i次循环把第 i 个链表和 ans 合并,答案保存到 ans 中。
class Solution { public: // 合并两个有序链表 ListNode* mergeTwoLists(ListNode* a, ListNode* b) { // 如果其中一个链表为空,则直接返回另一个链表 if ((!a) || (!b)) return a ? a : b; ListNode head, *tail = &head, *aPtr = a, *bPtr = b; while (aPtr && bPtr) { if (aPtr->val < bPtr->val) { // 当a链表的值小于b链表的值时,将a链表的节点拼接到结果链表后面,并更新a链表的指针 tail->next = aPtr; aPtr = aPtr->next; } else { // 当a链表的值大于等于b链表的值时,将b链表的节点拼接到结果链表后面,并更新b链表的指针 tail->next = bPtr; bPtr = bPtr->next; } // 更新结果链表的尾指针 tail = tail->next; } // 将剩下的未合并的链表直接拼接到结果链表的尾部 tail->next = (aPtr ? aPtr : bPtr); // 返回结果链表 return head.next; } // 合并K个有序链表 ListNode* mergeKLists(vector<ListNode*>& lists) { ListNode *ans = nullptr; for (size_t i = 0; i < lists.size(); ++i) { // 逐个合并链表,将结果保存到ans中 ans = mergeTwoLists(ans, lists[i]); } // 返回最终的合并结果 return ans; } };
方法二:分治合并
考虑优化方法一,用分治的方法进行合并。
class Solution { public: // 合并两个有序链表 ListNode* mergeTwoLists(ListNode *a, ListNode *b) { if ((!a) || (!b)) return a ? a : b; // 如果其中一个链表为空,则直接返回另一个链表 ListNode head, *tail = &head, *aPtr = a, *bPtr = b; while (aPtr && bPtr) { if (aPtr->val < bPtr->val) { // 比较两个节点的值,将较小的节点接入结果链表 tail->next = aPtr; aPtr = aPtr->next; // 更新a链表指针 } else { tail->next = bPtr; bPtr = bPtr->next; // 更新b链表指针 } tail = tail->next; // 更新结果链表的尾节点 } tail->next = (aPtr ? aPtr : bPtr); // 将剩余未合并完成的节点接入结果链表 return head.next; // 返回结果链表的头节点 } // 递归合并多个有序链表 ListNode* merge(vector <ListNode*> &lists, int l, int r) { if (l == r) return lists[l]; // 当l和r相等时,表示只有一个链表,直接返回该链表 if (l > r) return nullptr; // 当l大于r时,表示没有待合并的链表,返回空指针 int mid = (l + r) >> 1; // 计算中间位置 return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r)); // 递归合并左右两个部分 } ListNode* mergeKLists(vector<ListNode*>& lists) { return merge(lists, 0, lists.size() - 1); // 调用递归函数,合并整个链表数组 } };
方法三:使用优先队列合并
这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,k 个链表就最多有 k 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。
class Solution { public: // 定义结构体Status,用于在优先队列中存储节点信息 struct Status { int val; // 节点的值 ListNode *ptr; // 节点指针 bool operator < (const Status &rhs) const { return val > rhs.val; // 优先队列按照节点值的大小进行排序 } }; priority_queue<Status> q; // 创建优先队列q,按照节点值的大小进行排序 ListNode* mergeKLists(vector<ListNode*>& lists) { // 将每个链表的第一个节点放入优先队列 for (auto node : lists) { if (node) q.push({node->val, node}); } ListNode head, *tail = &head; // 创建结果链表的头节点head和尾节点tail while (!q.empty()) { auto f = q.top(); q.pop(); tail->next = f.ptr; // 将当前队列中最小的节点连接到结果链表的尾部 tail = tail->next; // 更新尾节点为当前节点 if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next}); // 如果当前节点还有下一个节点,则将下一个节点放入优先队列继续排序 } return head.next; // 返回结果链表的头节点 } };