赞
踩
涉及知识点:链表头插法、归并、优先队列(堆)
分析:
思路:一种简单的思路就是每次进行两两合并,比如以第一个链表最为最终链表,那么把剩下的链表都合并到第一个链表上来。由于第一个链表可能为空,因此需要设计一个哨兵节点用来处理这种情况。还需要判断一下数组中没有链表和只有一个链表的情况。
这种方法的缺点就是要扫描第一个链表很多遍,会产生重复的对比,因此时间复杂度比较高,但是不需要额外的空间,因此空间复杂度较低。
- class Solution {
- public:
- // 两个链表的合并函数
- void merge(ListNode* list1, ListNode* list2)
- {
- // 我们插入节点,因此需要判断的是list1的next节点,而初始化的list1就是哨兵节点
- while (list1->next != nullptr && list2 != nullptr)
- {
- // 只要list2指向节点的值小于等于list1的next节点的值,就将其插入到list1与list1的next节点的中间
- if (list2->val <= list1->next->val) {
- ListNode* tmp = list2;
- list2 = list2->next;
- tmp->next = list1->next;
- list1->next = tmp;
- list1 = list1->next;
- } // 若list2指向节点值大于list1的next节点的值,就向后移动list1节点
- else
- list1 = list1->next;
- }
- // 若list1走到了最后一个节点,那么就把list2指向的后面所有节点接在list1的后面
- if (list1->next == nullptr)
- list1->next = list2;
- }
-
- ListNode* mergeKLists(vector<ListNode*>& lists) {
- // 存储链表数组的长度以及哨兵节点
- int sz = lists.size();
- ListNode* sentry = new ListNode(-1);
- // 若链表数组长度为0或为1,直接返回相应结果
- if (lists.size() == 0)
- return nullptr;
- if (lists.size() == 1)
- return lists[0];
- // 数组长度大于1,则将数组中第一个链表作为最后返回的链表,并把它接在哨兵节点后
- sentry->next = lists[0];
- // 从第二个链表开始,依次合并每一个链表
- for (int i = 1; i < sz; ++i)
- {
- merge(sentry, lists[i]);
- }
- // 返回哨兵节点的next,即合并后的链表
- return sentry->next;
- }
- };
思路:我们可以采用归并的算法使得链表两两合并,这样能够使得合并步骤的时间复杂度由k降为logk级别,因此下面采用归并的方法合并。
- class Solution {
- public:
- ListNode* merge(vector<ListNode*> & lists, int start, int end)
- {
- // 若end和start相等,说明只有一个链表,那么返回这个链表即可
- if (end == start) {
- return lists[start];
- } // 若不等则分为下面的情况
- else {
- // 定义链1和链2,定义resHead作为哨兵节点,prev用于实现尾插法
- ListNode* list1;
- ListNode* list2;
- ListNode* resHead = new ListNode(0);
- ListNode* prev = resHead;
- // 若只有数组中只有两个元素,那么就把链1和链2设置为这两个链表
- if (end - start == 1) {
- list1 = lists[start];
- list2 = lists[end];
- } // 否则将数组分为两半,递归求左边的合并结果和右边的合并结果
- else {
- int mid = (start+end)/2;
- list1 = merge(lists, start, mid);
- list2 = merge(lists, mid+1, end);
- }
- // 将list1和list2进行合并
- while (list1 != nullptr && list2 != nullptr)
- {
- ListNode* tmp;
- if (list1->val <= list2->val) {
- tmp = list1;
- list1 = list1->next;
- tmp->next = prev->next;
- prev->next = tmp;
- prev = prev->next;
- } else {
- tmp = list2;
- list2 = list2->next;
- tmp->next = prev->next;
- prev->next = tmp;
- prev = prev->next;
- }
- }
- // 若某条链已空,则把prev的next接上另一条链即可
- if (list1 == nullptr)
- prev->next = list2;
- if (list2 == nullptr)
- prev->next = list1;
- // 返回哨兵节点的next
- return resHead->next;
- }
- }
-
- ListNode* mergeKLists(vector<ListNode*>& lists) {
- int sz = lists.size();
- // 若数组中没有链表,那么返回空
- if (sz == 0)
- return nullptr;
- // 对数组进行归并,返回最终结果
- ListNode *res = merge(lists, 0, sz-1);
- return res;
- }
- };
思路:既然我们需要合并链表,那我们也可以同时比较。对于数组中每个链表我们在头部都放置一个指针,将最小的元素放进结果链表中。那么只要所有链表都遍历到尾了,我们就得到了结果。那么为了快速找出最小的元素,我们可以借助堆或者优先队列(本质上就是堆)来实现,这里采用STL中的优先队列。
- class Solution {
- public:
- // 比较函数,用于比较ListNode*类型大小,要定义为static否则下面无法调用
- // 因为这个成员函数并不需要this指针,所以要设置为静态成员函数
- static bool compare(const ListNode* lhs, const ListNode* rhs)
- {
- return lhs->val > rhs->val;
- }
-
- ListNode* mergeKLists(vector<ListNode*>& lists) {
- // 先定义数组大小sz以及哨兵节点res,prev节点保存插入点的前驱节点
- int sz = lists.size();
- ListNode* res = new ListNode(0);
- ListNode* prev = res;
- // 优先队列,其中模板的第三个参数可以也写为 decltype(compare)*
- priority_queue<ListNode*, vector<ListNode*>,
- bool (*)(const ListNode*, const ListNode*)> pq(compare);
- // 首先把所有链表的头节点加入优先队列中(只要非空)
- for(int i = 0; i < sz; ++i)
- {
- if (lists[i] != nullptr)
- pq.push(lists[i]);
- }
- // 若队列非空,则将最小的节点接在prev的后面,将队头元素出队
- // 接着将最小节点的next入队(只要非空)
- while (!pq.empty())
- {
- prev->next = pq.top();
- pq.pop();
- prev = prev->next;
- if (prev->next != nullptr)
- pq.push(prev->next);
- }
- // 返回哨兵节点的next即最终链表
- return res->next;
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。