Leetcode刷题笔记之链表篇21. 合并两个有序链表

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode刷题笔记之链表篇21. 合并两个有序链表相关的知识,希望对你有一定的参考价值。

😈博客主页:🐼大家好我叫张同学🐼
💖 欢迎点赞 👍 收藏 💗留言 📝 欢迎讨论! 👀
🎵本文由 【大家好我叫张同学】 原创,首发于 CSDN 🌟🌟🌟
精品专栏(不定时更新) 【数据结构+算法】 【做题笔记】【C语言编程学习】
☀️ 精品文章推荐
【C语言进阶学习笔记】三、字符串函数详解(1)(爆肝吐血整理,建议收藏!!!)
【C语言基础学习笔记】+【C语言进阶学习笔记】总结篇(坚持才有收获!)


前言

为什么要写刷题笔记
写博客的过程也是对自己刷题过程的梳理总结,是一种耗时有效的方法。
当自己分享的博客帮助到他人时,又会给自己带来额外的快乐和幸福。
(刷题的快乐+博客的快乐,简直是奖励翻倍,快乐翻倍有木有QAQ🙈)

题目内容

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。


原题链接(点击跳转)

暴力求解法

我们可以将list2选为合并后待返回的链表,然后将list1中的结点合并到list2中。

算法图解

函数实现
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    struct ListNode *cur1 = list1,*cur2 = list2;
    //以list2作为最终合并后的链表,当list2为空表,直接返回list1
    if(cur2 == NULL)
        return list1;
    while(cur1)
     //先保存cur1的下个结点位置,避免丢失
    struct ListNode* next1 = cur1->next,*next2 = cur2->next;
         while(next2 && cur2->val < cur1->val && next2->val < cur1->val)
            cur2 = next2;
            next2 = cur2->next;
           
        if(cur2->val > cur1->val)//头插
            cur1->next = cur2;
            list2 = cur1;
            cur2 = list2;
            cur1 = next1;
            continue;
       
        cur1->next = cur2->next;
        cur2->next = cur1;
        cur1 = next1;
    
    return list2;

注意:在实际写代码的时候,我们会发现仅有一个next用来保存cur1的下一个结点是不够的。cur2的下一个结点的信息也需要保存(假设下一个结点不为空,若为空直接cur1结点插入cur2后即可),因为在比较的时候,当cur2->val小于cur1->valcur2->next->val大于cur1->val时(cur2->next->val就是next2->val),才能将list1cur1指向的结点插入到list2中。否则,cur2需要向后移动,直到满足条件为主。

小技巧
很多OJ题的测试用例非常多,即便我们画图分析了,可能还有很多场景没能想到。这个时候就需要借助OJ中错误的测试用例来帮助我们完善调整优化代码。


这道题目很多场景我都是通过失败的测试用例才发现的,比如说:
list1不为空,list2为空,这时候需要直接返回list1,无需合并。
list1中的很多结点一开始就小于list2中的结点,这个时候就不能仅用尾插或者中间插入实现,就需要头插~

整体而言,这种暴力求解的方式写起来并不轻松,有时候需要考虑的条件和场景可能比其他方式更多!!!
接下来我们将学习一种更简单、更易于实现的方式~


新建链表法

为了方便合并链表,我们可以新建一个链表newlist,然后每次从list1list2中将val值小的结点尾插newlist中。

算法图解

函数实现
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    struct ListNode* newlist = NULL,*tail = newlist;
    struct ListNode *cur1 = list1,*cur2 = list2;
    if(list1 == NULL && list2 == NULL)
         return NULL;
    else if(list1 == NULL)
         return list2;
    else if(list2 == NULL)
         return list1;
    while(cur1 && cur2)
        struct ListNode* next1 = cur1->next;
        struct ListNode* next2 = cur2->next;
        if(cur1->val <= cur2->val)
            if(tail == NULL)//newlist为NULL单独处理
                cur1->next = tail;
                newlist = cur1;
                tail = newlist;
            
            else
                cur1->next = tail->next;
                tail->next = cur1;
                tail = tail->next;
            
            cur1 = next1;
        
        else
                if(tail == NULL)//newlist为NULL单独处理
                cur2->next = tail;
                newlist = cur2;
                tail = newlist;
            
            else
                cur2->next = tail->next;
                tail->next = cur2;
                tail = tail->next;
             
            cur2 = next2;
        
    
    if(cur1)
       tail->next = cur1;
    else
       tail->next = cur2;
    return newlist;


这种方式比暴力求解的方法好了很多,至少没有那么多场景(什么时候要头插,什么时候中间插入等等)需要考虑,但是仍然有优化的空间。我们可以看到当新建的链表newlist为不带哨兵位guard的链表时,每次都需要去考虑newlist == NULL这个条件,真是烦死人啦!为此,我们对以上代码进行优化,创建一个带哨兵位(带头)的链表newlist

代码优化
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
    //创建哨兵位头节点,不存放有效数据
    struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* newlist = guard,*tail = newlist;
    struct ListNode *cur1 = list1,*cur2 = list2;
    if(list1 == NULL && list2 == NULL)
         return NULL;
    else if(list1 == NULL)
         return list2;
    else if(list2 == NULL)
         return list1;
    while(cur1 && cur2)
        struct ListNode* next1 = cur1->next;
        struct ListNode* next2 = cur2->next;
        if(cur1->val <= cur2->val)
            cur1->next = tail->next;
            tail->next = cur1;
            tail = tail->next;
            cur1 = next1;
        
        else
            cur2->next = tail->next;
            tail->next = cur2;
            tail = tail->next;
            cur2 = next2;
        
    
    if(cur1)
       tail->next = cur1;
    else
       tail->next = cur2;
    //释放哨兵位头节点
    newlist = newlist->next;
    free(guard);
    return newlist;


原创不易,求点赞+关注+收藏~

以上是关于Leetcode刷题笔记之链表篇21. 合并两个有序链表的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode刷题笔记之链表篇141. 环形链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇234. 回文链表

Leetcode刷题笔记之链表篇206. 反转链表

Leetcode刷题笔记之链表篇141. 环形链表