您现在的位置是:首页 >技术教程 >【LeetCode】23. 合并 K 个升序链表网站首页技术教程

【LeetCode】23. 合并 K 个升序链表

Schanappi 2024-10-01 00:01:02
简介【LeetCode】23. 合并 K 个升序链表

23. 合并 K 个升序链表(困难)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法一:顺序合并

思路

在这里插入图片描述
在这里插入图片描述

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;
        } else {
            tail->next = bPtr; bPtr = bPtr->next;
        }
        tail = tail->next;
    }
    tail->next = (aPtr ? aPtr : bPtr);
    return head.next;
}

在这里插入图片描述

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* merge2Lists(ListNode* a, ListNode* b){
        // 如果二者中有空链表 返回非空链表
        if((!a) || (!b))    return a ? a : b;
        ListNode head, *tail = &head, *aptr=a, *bptr=b;
        while(aptr && bptr){
            // a的值比较小 加入a
            if(aptr->val < bptr->val){
                tail->next = aptr;
                aptr = aptr-> next;
            }
            // b的值比较小
            else{
                tail->next = bptr;
                bptr = bptr->next;
            }
            tail = tail -> next;
        }
        tail->next = (aptr ? aptr : bptr);
        // 因为head是节点 所以用.访问
        return head.next;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) { 
        ListNode *ans=nullptr;
        for(int i=0; i<lists.size(); ++i){
            // 将ans和list[i]逐一合并
            ans = merge2Lists(ans, lists[i]);
        }
        return ans;
    }
};

方法二:分治

思路

在这里插入图片描述
在这里插入图片描述

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* merge2Lists(ListNode* a, ListNode* b){
        // 如果二者中有空链表 返回非空链表
        if((!a) || (!b))    return a ? a : b;
        ListNode head, *tail = &head, *aptr=a, *bptr=b;
        while(aptr && bptr){
            // a的值比较小 加入a
            if(aptr->val < bptr->val){
                tail->next = aptr;
                aptr = aptr-> next;
            }
            // b的值比较小
            else{
                tail->next = bptr;
                bptr = bptr->next;
            }
            tail = tail -> next;
        }
        tail->next = (aptr ? aptr : bptr);
        // 因为head是节点 所以用.访问
        return head.next;
    }
    ListNode* merge(vector<ListNode*> a, int l, int r){
        if(l == r) return a[l];
        if(l > r)   return nullptr;
        int mid = (l + r) / 2;
        return merge2Lists(merge(a, l, mid), merge(a, mid+1, r));
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) { 
        return merge(lists, 0, lists.size()-1);
    }
};

方法三:优先队列

  • 什么是优先队列

    优先队列是一种容器适配器,采用了这样的数据结构,保证了第一个元素总是整个优先队列中最大的(或最小的)元素

    优先队列默认使用vector作为底层存储数据的容器,在vector上使用了堆算法将vector中的元素构造成堆的结构,所以其实我们就可以把它当作堆,凡是需要用堆的位置,都可以考虑优先队列。

  • priority_queue类模板参数

    template <class T, class Container = vector<T>,
    class Compare = less<typename Container::value_type> >
    
    class priority_queue;
    
    • class T:T是优先队列中存储的元素的类型

    • class Container = vector< T>:Container是优先队列底层使用的存储结构,可以看出来,默认采用vector。

    • class Compare = less< typename Container::value_type> :Compare是定义优先队列中元素的比较方式的类。默认是按小于(less)的方式比较,创建出来的就是大堆。所以优先队列默认就是大堆。如果需要创建小堆,就需要将less改为greater

      下面是less类的内部函数,less类的内部重载(),参数列表中有左右两个参数,左边小于右边的时候返回true,此时优先队列就是大堆。

      template <class T> 
              struct less : binary_function <T,T,bool> {
                bool operator() (const T& x, const T& y) const {return x<y;}
      };
      

      注意:less类和greater类只能比较内置类型的数据的大小,如果用户需要比较自定义类型的数据,就需要自己定义一个比较类,并且重载()

      同时less类和greater类也具有模板参数,因为他们也是模板,所以我们如果要存储自定义类型的元素,就要将自定义类型作为模板参数传递给less类和greater类

  • 示例

    因为priority_queue是模板,所以创建对象时需要传入模板参数,但是由于模板参数内部是具有默认值的,所以创建大堆时可以只传递元素类型即可。但创建小堆的时候,模板参数是不可以省略的。

    //完整版按大堆创建对象
    priority_queue<int,vector<int>, less<int>> q;
    
    //按小堆创建对象(按小堆创建时参数列表不可以省略)
    priority_queue<int, vector<int>, greater<int>> q;
    

在这里插入图片描述

  • 注意,因为 Comp 函数默认是对最大堆进行比较并维持递增关系,如果我们想要获取到最小的节点值,则需要实现一个最小堆,因此比较函数应该维持递减关系,所以 operator() 中返回时用大于号进行比较。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
// 比较模板
struct Comp{
    bool operator() (ListNode* l1, ListNode* l2){
        // 最小堆 
        return l1->val > l2->val;
    }   
};
    ListNode* mergeKLists(vector<ListNode*>& lists) { 
        if(lists.empty()) return nullptr;

        // 最小堆的优先队列,所以模板不可以省略
        priority_queue<ListNode*, vector<ListNode*>, Comp> q;

        // 只存入每个链表的第一个元素
        for(ListNode* list:lists){
            // 注意要非空
            if(list)
                q.push(list);
        }

        // dummy 虚拟节点,最终返回它的下一个位置
        ListNode *dummy = new ListNode(0), *cur = dummy;
        while(!q.empty()){
            cur->next = q.top();
            q.pop();
            cur = cur -> next;
            // 即q.top()所在链表还没有遍历结束
            // 那么需要存入它的下一个节点
            if(cur->next){
                q.push(cur->next);
            }
        }
        return dummy->next;
    }
};

参考资料

  1. 官方题解
  2. C++ 优先队列 priority_queue 使用篇
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。