玩转链表之链表合并

一、合并两个排序的链表

解法一:迭代(推荐)

  • 首先考虑空链表的情况,只要有一为空,答案即为另一链表
  • 设置新链表表头,连接两个链表排序后的节点
  • 遍历两个链表,取值较小的追加在新链表后,节点指针后移更新
  • 剩余节点之间连接即可
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1==NULL)//先考虑为空的情况
            return pHead2;
        if(pHead2==NULL)
            return pHead1;
            
        ListNode*dummy=new ListNode(-1),*cur=dummy;//设置表头
        while(pHead1&&pHead2)//两链表都不为空
        {
            if(pHead1->val<=pHead2->val)/取值较小的
            {
                cur->next=pHead1;//连接
                pHead1=pHead1->next;//后移更新节点
            }
            else
            {
                cur->next=pHead2;
                pHead2=pHead2->next;
            }
            cur=cur->next;//更新当前指针
        }
        cur->next=pHead1?pHead1:pHead2;//连接剩余部分
        return dummy->next;
    }
};

** 时间复杂度:O(n),最坏情况遍历两个链表的所有节点,
空间复杂度:O(1)**

解法二:递归

  • 每次比较两链表节点的值,取较小的链表指针向后移动,另一个进入递归
  • 将递归的结果连接在较小值节点的后面,等效于在较小值后面添加节点
  • 递归终止条件为两链表为空
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1==NULL)
            return pHead2;
        if(pHead2==NULL)
            return pHead1;
        if(pHead1->val<=pHead2->val)
        {
            pHead1->next=Merge(pHead1->next,pHead2);//连接递归后的节点,每次更新较小值的指针
            return pHead1;//返回表头
        }
        else
        {
            pHead2->next=Merge(pHead1,pHead2->next);
            return pHead2;
        }
    }
};

递归思想由小及大,是一个累计的过程,设定一种模式让其重复执行
时间复杂度:O(n),最坏情况遍历两个链表的所有节点,
空间复杂度:O(n),递归栈长度最大为n

解法三:容器

  • 设置虚拟节点用于连接节点
  • 将节点存入list容器
  • 排序取出连接构造新链表(注意自定义排序)
class cmp
{
public:
    bool operator()(ListNode*l1,ListNode*l2)
    {
        return l1->val>l2->val;
    }
};
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1==NULL)
            return pHead2;
        if(pHead2==NULL)
            return pHead1;
        ListNode*dummy=new ListNode(-1),*cur=dummy;
        list<ListNode*>l;
        while(pHead1)
        {
            l.push_back(pHead1);
            pHead1=pHead1->next;
        }
        while(pHead2)
        {
            l.push_back(pHead2);
            pHead2=pHead2->next;
        }
        l.sort(cmp());
        int len=l.size();
        for(int i=0;i<len;i++)
        {
            cur->next=l.back();
            l.pop_back();
            cur=cur->next;
        }
        cur->next=NULL;
        return dummy->next;
    }
};

时间复杂度:O(n),遍历两个链表的所有节点,
空间复杂度:O(n),容器占用两链表大小的空间

二、合并K个已排序的链表

解法一:归并排序思想

  • 对于k个链表,相当于k个合并数列的子问题,需要两个合并,不断向上合并为一个完整的链表
  • 从链表的首、尾开始,每次从中间划分为两半
  • 将这两半子问题合并好后就有了两个有序链表,最后将这两链表合并就行了,依据子问题递归处理
  • 当左右区间相等或左边大于右边时,终止合并
class Solution {
public:
    ListNode*Merge(ListNode*pHead1,ListNode*pHead2)//这即是题目一中的两链表合并
    {
        if(pHead1==NULL)
            return pHead2;
        if(pHead2==NULL)
            return pHead1;
        ListNode*head=new ListNode(-1),*cur=head;
        while(pHead1&&pHead2)
        {
            if(pHead1->val<=pHead2->val)
            {
                cur->next=pHead1;
                pHead1=pHead1->next;
            }
            else
            {
                cur->next=pHead2;
                pHead2=pHead2->next;
            }
            cur=cur->next;
        }
        cur->next=pHead1?pHead1:pHead2;
        return head->next;
    }
    ListNode*divideMerge(vector<ListNode*>&lists,int left,int right)//划分合并区域
    {
        if(left>right)
            return NULL;
        if(left==right)//当取到中间一个的情况时返回中间节点
            return lists[left];
        int mid=(left+right)/2;//从中间分成两段,再合并分好的两段
        return Merge(divideMerge(lists, left, mid),divideMerge(lists, mid+1, right));
    }
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        return divideMerge(lists, 0, lists.size()-1);//k个链表归并排序
    }
};

时间复杂度:O(n*k),n为所有链表总节点数,最坏每次合并都为O(n),分治为二叉树型递归,每个节点都要使用一次合并,要合并k-1次
空间复杂度:O( log ⁡ 2 \log_2 log2k),最坏情况下递归 log ⁡ 2 \log_2 log2k层,占用 log ⁡ 2 \log_2 log2k的递归栈

解法二:优先队列

优先可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

  • 首先构造一个比较链表节点大小的小顶堆
  • 先遍历k个链表头,将非空节点加入优先队列
  • 依次弹出优先队列里的最小元素,将其连接在合并后的链表后面,随后将该节点的原本后继节点(不为空)加入队列,类似上面双指针
class cmp
{
public:
    bool operator()(ListNode*l1,ListNode*l2)//设置排序方式,降序
    {
        return l1->val>l2->val;
    }
};
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        priority_queue<ListNode*,vector<ListNode*>,cmp>pq;//小顶堆
        for(int i=0;i<lists.size();i++)
        {
            if(lists[i]!=NULL)//将所有不为空的链表头加入到小堆中
                pq.push(lists[i]);
        }
        ListNode*dummy=new ListNode(-1),*cur=dummy;//设置虚拟头
        while(!pq.empty())//直到小堆为空
        {
            ListNode*tmp=pq.top();//取出最小元素
            pq.pop();
            cur->next=tmp;//连接
            cur=cur->next;
            if(tmp->next!=NULL)//每次再取出链表后一个元素加入节点,进行排序
                pq.push(tmp->next);
            
        }
        return dummy->next;
    }
};

时间复杂度:O(n log ⁡ 2 \log_2 log2k),n为链表节点总数,最坏遍历所有节点,每次加入优先队列排序要O( log ⁡ 2 \log_2 log2k)
空间复杂度:O(K),优先队列大小不超过k

二再一的基础上设置多个链表,考察对算法和数据结构的使用

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

...404 Not Found

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值