单链表相关习题(超详细!)(1)

前言:

  小编最近做了很多关于单链表的习题,为了防止我以后的忘记,于是我决定把每个我认为重要的习题做成博客,包装成2 ~ 3篇来进行书写,它们大多来自力扣,少部分来自牛客,今天的文章都是来自力扣的oj题,下面开始我们今天的习题之旅!

目录:

1.移除链表的元素

2.反转链表

3.两个升序链表拼接成一个大的升序链表

正文:

1.移除链表的元素

  在讲解习题之前,小编先给上这个原题的详细内容:

203. 移除链表元素 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/remove-linked-list-elements/description/

  从这个题目的介绍我们可以知道,这个题目是想要删除拥有指定数的结点,这个题有着很多的解法,小编选择的解法就是创建一个新的单链,通过循环的知识,让单链表一直往后面循环,如果碰到的结点不是指定整数的结点,那么就把这个结点复制给新节点,这里我们需要设置三个指针,它们的作用分别是:1.为了不让头结点变化,代替头结点进行循环;2.作为新链表的头结点。3.代替新节点进行循环,我们在进入循环的时候需要【】判断新节点是否为空,如果为空直接把原链表的头结点给予新节点就好,循环结束以后,我们就可以得到一个没有指定数据的单链表,下面小编开始通过图文的方式进行分别解释了(以上面为例子)!

  先来看原链表的第一个结点的数据,不是指定数据,可以进行复制:

  之后我们通过循环从下一个结点开始看,也不是指定数据,可以复制:

  在看下一个结点,发现是指定数据,这里直接不复制了:

   经过一系列的循环以后,我们便可以得到一个船新的单链表:

    所以由此可以看出,循环结束的方式就是pour不能为空,为空循环结束,可能有一些读者朋友已经迫不及待的去直接返回新节点的头了,那可就大错特错了,我们在刚进入循环的时候,是不是把原链表的结点给新链表了?所以此时 ,如果新链表最后一个结点不是原链表最后一个结点的话,那么新链表的最后一个结点还会是原来链表的下一个结点!所以我们首先要把新链表最后一个节点的下一个指针指向空,这个时候我们就可以返回头结点了!下面是代码展示图,以及力扣最后给的结果:

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode *newhead = NULL, *newlist = NULL;
    ListNode* pour = head;
    while (pour) {

        if (pour->val != val) 
        {
            if (newhead == NULL)
             {
                newhead = newlist = pour;
            } 
            else
            {
                newlist->next = pour;
                newlist = newlist->next;
            }
        }
        pour = pour->next;
    }
    if (newlist != NULL)
        newlist->next = NULL;
    return newhead;
}

 2.反转链表

  下面我们快马加鞭开始看反转链表喽!下面先给上题目:206. 反转链表 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-linked-list/description/

   根据题目的描述,我们可以知道,这个题目想让我们把单链表的方向给倒装过来,从来实现反转操作,这个题目同样的也会有很多解法,算法题都是这样的,这个题目可以采取之前小编博客所写的单链表的头插来完成,不过小编觉着这样做着有点麻烦,下面小编将会给出一个小编认为很牛的解法,相信各位读者朋友看了这个解法也会觉着这个解法yyds,上车喽!别掉队!

  小编想要选择用三个指针来实现反转操作,这三个指针,分别指向空(n1),头结点(n2),头结点的下一个结点(n3),为什么要这么设置呢?下面请看小编的图文解释

  首先,我们可以先让n2的下一个节点指向n1,再让n1,n2,n3往后走,这里会实现第一个结点转向:

  

  之后我们重复上面的操作,继续让n2的下一个结点指向n1,然后三个结点同时往后走:

   中间过程小编就省略了,和上面是一样的,看一看最后会怎么样:

   这里我们便可以实现反转操作,不难看出,此时n1变成了新链表的头,下面就要考虑代码的书写了(感觉上面先写代码再给图对读者朋友不太友好,所以我改一改风格),这里我们需要用到循环的操作,我们可以把n2作为循环的条件,当n2变成空的时候就可以停止循环了,之后我们可以实现小编上面说的,讲n2的下一个结点变成n1,然后三个指针同时往后走,这里我们需要注意n3,因为当n2为最后一个结点的时候,n3就已经是空了,如果还想让它往后走,会变成对空指针进行解引用,代码会直接报错的,所以我们需要用到if语句来判断n3,如果不为空才可以变成下一个结点,具体流程就是我说的这样,下面小编直接上代码:

 typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
    {
        return head;
    }
    ListNode* n1 = NULL,*n2 = head,*n3 = n2 -> next;
    while(n2)
    {
       n2 -> next = n1;
       n1 = n2;
        n2 = n3;
        if(n3 !=NULL)
        n3 = n3 -> next;
    }
    return n1;
}

 3.两个升序链表拼接成一个大的升序链表

  我们来看最后一道算法题,这个题比较麻烦,下面先展示题目:21. 合并两个有序链表 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/merge-two-sorted-lists/description/

  正如小编所说,这个题目其实是很复杂的,当然,这个题也是有很多的解法,小编就先来说一个解法,这个解法也许不是最优秀的,但确实是好用,好理解的:

  小编的方法是创造一个新的单链表,这个链表用来作为最后一个大的链表,首先,我们需要在创建两个指针,分别来来表示两个链表的头结点(pour1,pour2),之后我们需要进行二个结点数据的比较,如果第一个链表的结点小于等于第二个链表的结点,那么讲第一个链表的数据放入到新链表中,如果新链表是空的话,直接头结点给第一个链表,否则新链表当前结点下一个结点就是第一个链表的结点,然后让第一个链表往后走,新链表往后走,第二个链表不动;反之就是把刚才小编说过的话把第一个链表都改成第二个链表(第二个链表小于第一个链表)。下面小编通过图文的方式来告诉各位原因:

  首先,先比较第一个结点,不难发现,第一个链表的结点的数据小于等于第二个结点,由于新链表是空,所以我们直接把第一个数据给于新链表:

     之后继续第一个链表结点往后走,继续比较,不难看出第二个链表的结点的数据要小于第一个链表结点的数据,所以我们把第二个链表的结点接给新节点:

  经过一系列的交换以后,head1已经到了NULL,此时我们需要停止循环:

  之后我们将先结点的下一个数据直接指向剩余的那个链表剩余的结点就好,此时我们就完成了两个升序链表合并成一个大的升序链表操作!下面小编给出代码讲解环节!

  首先,我们需要创建若干个指针,分别是pour1(存放head1),pour2(存放head2),newhead(新链表头指针),newpour(新链表来代替头指针循环的指针),之后我们需要判断head1,或者head2是否为空,如果一个为空,那么直接把另一个直接return掉,因为上面图像操作涉及到了指针的解引用,如果不先判断的话,那么可能会有对空指针进行解引用的操作,所以我们需要先留个心眼去判断一下,下面开始进入重头戏了,上面代码的书写牵扯到了循环,那么我们得先思考循环的条件到底是什么?小编在上面以及给出了提示,如果head1或者head2其中一个首先变成了空节点,那么我们就会结束循环,现在看循环的内部,这个其实小编在上面已经涉及到了,首先我们可以判断出我们需要head1和head2结点存放的数据进行比较,之后根据谁小谁就先行一步存放到新链表的原则,一步一步循环就好,不过我们首先要判断新链表是否为空,如果为空直接把结点传给新指针就好,我们再循环结束以后,还需要判断head1和head2哪一个不为空,哪一个不为空直接让新链表的尾结点的下一个结点指向哪个结点就好了,下面小编给出代码图·:

 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list2 == NULL)
    {
        return list1;
    }
    if(list1 == NULL)
    {
        return list2;
    }
    ListNode * pour = list1,*plist = list2,*pcur = NULL,*pphed = NULL;
    while(pour != NULL && plist != NULL)
    {
        if(pour -> val <= plist -> val)
        {
            if(pcur == NULL)
            {
                pphed = pcur = pour;
            }
            else
            {
                pcur -> next = pour;
                            pcur = pcur -> next;
            }
            pour = pour -> next;
        }
        else
        {
            if(pcur ==  NULL)
            {
               pphed =  pcur = plist;
            }
            else
            {
                pcur -> next = plist;
                            pcur = pcur -> next;
            }
            plist = plist -> next;
        }
    }
    if(plist)
    {
        pcur -> next = plist;
    }
    if(pour)
    {
        pcur -> next = pour;
    }
    return pphed;
}

总结:

  上面就是小编首先要讲的几个习题,小编这是第二次讲述这种算法题了,可能有一些不太=完善,恳请大家见谅,如果有错误可以在评论区指出,小编一定及时的答复,其实小编本来想讲述寻找中间节点的题目的,不过小编发现它与其他我想讲的题目有着很大的共同点,于是我准备下一篇出个比较牛的算法,敬请期待!那我们下一篇博客见啦!

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值