2024年最全【数据结构 C语言版】第三篇 单链表习题_数据结构c语言 单链表题

1.移除链表元素

题目

力扣题目:203.移除链表元素

image-20221003214619815

题解

(1)可以在单链表元素之前设置一个哨兵头,这样的话删除链表的头结点跟其他结点的方法一致。

(2)先找到要删除结点的前一个结点,然后前一个结点的next更新为要删除结点的next,释放要被删除的结点。

image-20221003220853824

(3)来看看如何设置一个虚拟头(哨兵头)。依然还是在这个链表中,移除元素1。

203_链表删除元素6

(4)图解

image-20221003222527041

实现注意点

(1)不过oj题中基本不带头,题目也没有说明链表带头所以我们需要自己创建一个哨兵头。

struct ListNode \* guard=(struct ListNode\*)malloc(sizeof(struct ListNode));

需要注意的是,最后哨兵头需要被释放,头结点更新,头结点指向哨兵结点的下一个。

 head=guard->next;
 free(guard);

代码

struct ListNode\* removeElements(struct ListNode\* head, int val){
    struct ListNode\* cur=head;
    //创造哨兵节点
    struct ListNode\* guard=(struct ListNode\*)malloc(sizeof(struct ListNode));
    struct ListNode\* tail=guard;
    while(cur)
    {
        if(cur->val!=val)
        {
            //后一个节点更新
            tail->next=cur;
            tail=tail->next;
            //前一个节点更新
            cur=cur->next;
        }
        else
        {
            struct ListNode\* del=cur;
            cur=cur->next;
            free(del);
        }
    }
    if(tail)
    tail->next=NULL;
    head=guard->next;
    free(guard);
    return head;
}

2.反转一个单链表

题目

力扣206. 反转链表

image-20221004064350279

题解(方法一)

(1)尾插变头插,取结点头插到新链表

image-20221004065541850

(2)代码细节过程图解

image-20221004070315875

方法一代码

struct ListNode\* cur=head;
struct ListNode\* newhead=NULL;
while(cur)
{
    struct ListNode\* next=cur->next;
    cur->next=newhead;
    newhead=cur;
    cur=next;
}
return newhead;

题解(方法二)

(1)翻转方向

image-20221004071645679

(2)代码实现细节图解

image-20221004072211861

方法二代码

struct ListNode\* reverseList(struct ListNode\* head){
    struct ListNode\* n1,\*n2,\*n3;
    //防止链表为空n3赋值放在循环里面
    n1=NULL,n2=head,n3=NULL;
    while(n2)
    {
        n3=n2->next;
        n2->next=n1;
        
        //迭代
        n1=n2;
        n2=n3;
    }
    return n1;
}   

3.链表的中间结点

题目

链表的中间结点

image-20221004072755957

题解

(1)通过快慢指针,可以只遍历一遍链表就能得出正确答案

(2)快指针一次走两步,慢指针一次走一步,快指针走完链表,慢指针刚好在一半处。

(3)图解

image-20221006155101974

代码

struct ListNode\* middleNode(struct ListNode\* head){
    //快慢指针
    struct ListNode\* f=head,\*s=head;
    //注意两个判断条件
    while(f&&f->next)
    {
        s=s->next;
        f=f->next->next;
    }
    return s;
}

4. 链表中倒数第k个结点

题目

[链表中倒数第k个结点_牛客题霸_牛客网 ](链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com))

题解

(1)方法还是快慢指针

(2)fast先走一步,fast跟slow再同时走,最后slow停在的地方就是倒数第一个。

类推,fast先走k步,fast跟slow再同时走,最后slow停在的地方就是倒数第几个。

(3)图解:
image-20221006163055677

注意

(1)为什么fast先走的循环中使用k–,使用–k可以吗?

代码while(k–),k–的含义:每次先用k,然后再自减。每次循环k的变化(以上图为例):3,2,1。当k==0的时候不执行。

当while(–k)时,–k的含义:每次用k的时候先自减。k的变化:2,1。导致就执行两次。

(2)链表可能为空需要判断。

代码

struct ListNode\* FindKthToTail(struct ListNode\* pListHead, int k ) {
    struct ListNode \*fast,\*slow;
    fast=slow=pListHead;
    //fast先走n步
    while(k--)
    {
        //当链表为空的时候,返回空值
        if(!fast)
        {
            return fast;
        }
        fast=fast->next;
    }
    //fast跟slow一直走到fast为空的时候
    while(fast)
    {
        fast=fast->next;
        slow=slow->next;
    }
    return slow;
}

5.合并两个有序链表

题目

合并两个有序链表

image-20221006164059127

题解

(1)写一个哨兵头,比大小,小的只管尾插,不需要考虑头插。

(2)图解

image-20221006165031320

代码

struct ListNode\* mergeTwoLists(struct ListNode\* list1, struct ListNode\* list2){
    struct ListNode\* guard =(struct ListNode\*)malloc(sizeof(struct ListNode));
    struct ListNode\* cur1,\*cur2,\*newhead,\*cur3;
    guard->next=NULL;
    cur1=list1,cur2=list2,newhead=cur3=guard;
    //小的尾插
    while(cur1&&cur2)
    {
        if(cur1->val>=cur2->val)
        {
            cur3->next=cur2;
            cur3=cur3->next;
            cur2=cur2->next;
        }
        else
        {
            cur3->next=cur1;
            cur3=cur3->next;
            cur1=cur1->next;
        }
    }
    //剩余的尾插
    if(cur1)
    {
        while(cur1)
        {
          cur3->next=cur1;
            cur3=cur3->next;
            cur1=cur1->next;
        }
    }
    else
    {
        while(cur2)
        {
            cur3->next=cur2;
            cur3=cur3->next;
            cur2=cur2->next;
        }
    }
    //释放哨兵结点,更新newhead的位置
    newhead=guard->next;
    free(guard);
    return newhead;
}

6.给定值x为基准将链表分割

编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结

点之前

题目

[链表分割](链表分割_牛客题霸_牛客网 (nowcoder.com))

image-20221006165558697

题解

(1)创建两个哨兵结点,一个哨兵结点(暂且叫它lessGuard)后面插入的是小于x值的结点,另一个哨兵结点(叫它greaterGuard)后面插入的是大于x值的结点。

(2)过程图解:

image-20221006185905606

注意

(1)greaterGuard指向的那条链表最后一个结点的next为什么要处理?

如果greaterGuard指向的那条链表的最后一个结点在原链表中不是最后一个结点的话,那么就会指向lessGuard指向的链表中的最后一个结点,如图所示:

image-20221006191754031

两个链表合并的话就会出现下面这张图的情况,即形成一个环:
image-20221006191940174

代码

    ListNode\* partition(ListNode\* pHead, int x) {
        // write code here
        
        //lessGuard:小于x的结点的链表的哨兵头,lessTail:用于操作链表
        //greaterGuard:大于x的结点的链表的哨兵头,greaterTail:用于操作链表
        //tail用于遍历原链表
        struct ListNode \*lessGuard,\*lessTail,\*greaterGuard,\*greaterTail,\*tail;
        lessGuard=(struct ListNode\*)malloc(sizeof(struct ListNode));
        greaterGuard=(struct ListNode\*)malloc(sizeof(struct ListNode));
        lessTail=lessGuard;
        greaterTail=greaterGuard;
        tail=pHead;
        //结点根据大小尾插到相应的链表后面
        while(tail)
        {
            if(tail->val<x)
            {
                lessTail->next=tail;
                tail=tail->next;
                lessTail=lessTail->next;
            }
            else
            {
                greaterTail->next=tail;
                tail=tail->next;
                greaterTail=greaterTail->next;
            }
        }
        //最后两个链表合并
        lessTail->next=greaterGuard->next;
        //greaterTail->next后面应该是NULL
        //否则可能形成环
        greaterTail->next=NULL;
        pHead=lessGuard->next;
        //释放掉两个哨兵结点
        free(greaterGuard);
        free(lessGuard);
        return pHead;
    }

7. 链表的回文结构

题目

[链表的回文结构](链表的回文结构_牛客题霸_牛客网 (nowcoder.com))

image-20221006190713684

题解

(1)可以先用快慢指针,找出链表的中间点,然后将后半段链表反转,最后两个链表进行比较。

(2)这题可以看成是第2题与第3题的结合。

(3)图解

image-20221006193451380

代码

bool chkPalindrome(ListNode\* A) {
        // write code here
        struct ListNode\* f,\*s;
        f=s=A;
        while(f&&f->next)
        {
            f=f->next->next;
            s=s->next;
        }
        
        struct ListNode \*cur=s, \*n = NULL;
        while(cur)
        {
            struct ListNode\* next=cur->next;
            cur->next=n;
            n=cur;
            cur=next;
        }

        struct ListNode \*cur1=A;
        while(n&&cur1)
        {
            if(n->val!=cur1->val)
            {
                return false;
            }
                n=n->next;
                cur1=cur1->next;
        }
        return true;
    }

8.相交链表

输入两个链表,找出它们的第一个公共结点。

题目

160. 相交链表

image-20221008092248171

题解

(1)a.先可以遍历一遍,如果两个链表尾结点的地址相同就说明两个链表相交。反之,则不想交。

b.通过a步骤的遍历,分别求出两个链表的长度,然后 长度长的链表先走差距步(两个链表的长度差)。走完之后,同时走,第一个地址相等的结点就是相交结点。

(2)图解:

image-20221008095020868

代码

struct ListNode \*getIntersectionNode(struct ListNode \*headA, struct ListNode \*headB) {
    //1.分别遍历
    struct ListNode\* curA,\*curB;


![img](https://img-blog.csdnimg.cn/img_convert/b226d0d8d8d7e8baeb8449ade1b16d70.png)
![img](https://img-blog.csdnimg.cn/img_convert/f315fc5643ea84691de36e37dde16c4f.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

说明两个链表相交。反之,则不想交。


b.通过a步骤的遍历,分别求出两个链表的长度,然后 长度长的链表先走差距步(两个链表的长度差)。走完之后,同时走,第一个地址相等的结点就是相交结点。


(2)图解:


![image-20221008095020868](https://img-blog.csdnimg.cn/img_convert/59a60ae42c344024424af3874ce4db6d.png)



> 
> 代码
> 
> 
> 



struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//1.分别遍历
struct ListNode* curA,*curB;

[外链图片转存中…(img-NcbUvIy7-1714673512455)]
[外链图片转存中…(img-d5Ud1N17-1714673512455)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值