链表OJ题讲解1

本文介绍了链表操作的几种常见方法,包括删除指定值的节点、反向链表、查找中间节点和倒数第k个节点,以及合并两个已排序链表。通过对比两种删除方法和两种链表反转策略,展示了不同的实现思路。
摘要由CSDN通过智能技术生成

💓博主个人主页:不是笨小孩👀
⏩专栏分类:数据结构与算法👀
🚚代码仓库:笨小孩的代码库👀
⏩社区:不是笨小孩👀
🌹欢迎大家三连关注,一起学习,一起进步!!💓


在这里插入图片描述

删除链表元素

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

方法一

我们可以遍历一遍链表,然后将等于val的值的节点删除了,然后在将剩余的链接起来即可。我们就需要两个指针,一个遍历链表另一个记录上一个节点的位置,如果相等就删除链接,等到链表遍历结束,就可以得到新的链表,但是这种放大有一个弊端,就是如果头结点就是val我们无法处理,所以我们需要现将头结点是val的情况处理了,头结点是val,我们只需要头删即可。

代码如下:

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* prev = NULL,*cur = head;
    while(cur)
    {
        if(cur->val==val)
        {
            //相等就要删除
            if(cur==head)
            {
                //如果头结点是就相等,就要头删
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                //正常删除
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
            // 不相等prev记录上一个节点,cur往下走
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

方法二

我们可以依旧是遍历原链表,但是我们这一次需要一个新的头,然后与val不相等的我们尾插到新的头中就可以了,这个方法比较省事,直接尾插就可以,但是需要注意新的头为空的情况,为了方便尾插,我们需要一个尾指针来记录尾节点的位置。

代码如下:

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head,*tail=NULL,*newhead=NULL;
    while(cur)
    {
        if(cur->val==val)
        {
            //相等就删除
            struct ListNode* next = cur->next;
            free(cur);
            cur = next;
        }
        else
        {
            //不相等就尾插
            if(newhead==NULL)
            {
                //第一次尾插
                newhead = tail = cur;
                cur= cur->next;
            }
            else
            {
                //正常尾插
                tail->next= cur;
                cur = cur->next;
                tail = tail->next;
            }
        }
    }
    //尾差的最有后一个节点的next要置NULL,不然可能会非法访问
    if(tail!=NULL)
    //tail等于空说明链表为空,或者链表中的元素都val
        tail->next = NULL;
    return newhead;
}

反向链表

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

方法一

我们定义一个空指针,然后翻转链表的指向,但是直接翻转的话我们会丢失下一个节点,所以我们还需要一个指针来记录下一个节点的位置。

代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* newhead = NULL,*cur = head,*next = NULL;
    while(cur)
    {
        //next指向下一个节点
        next = cur->next;
        //翻转指向
        cur->next = newhead;
        //更新新的头结点
        newhead = cur;
        //更新cur
        cur = next;        
    }
    return newhead;
}

方法二

头插法,我们遍历原链表,然后把节点都头插到新的头处,即可翻转链表。

代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* newhead = NULL,*cur = head;
    while(cur)
    {
        //保存下一个节点
        struct ListNode* next = cur->next;
        //头插
        cur->next = newhead;
        newhead = cur;
        cur = next;
    }
    return newhead;
}

链表的中间结点

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

这个题使用快慢指针就可以解决。我们让慢指针一次走一步,快指针一次走两步,我们就可以知道当快指针为空时,或者当快指针的next为空时此时的慢指针就是中间节点。

struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* slow = head,*fast = head;
    while(fast&&fast->next)
    {
    	//慢指针走一步
        slow=slow->next;
        //快指针走两步
        fast = fast->next->next;
    }
    return slow;
}

链表中倒数第k个结点

在这里插入图片描述

这个题同样使用快慢指针,我们可以让快指针先走k步,然后两个指针一起走,当快指针为空时,慢指针就是倒数第k个节点,如果先让快指针走k-1步的话,当快指针的next为空时,慢指针就是倒数第k个节点。这两种方法都可以。我们实现走k步的

代码如下:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    struct ListNode* fast = pListHead,*slow = pListHead;
    while(k--)
    {
        // fast=NULL前K个就为空了(判断k是不是过大,以及链表是不是空链表)
        if(fast==NULL) return NULL;
        fast = fast->next;
    }

    while(fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

合并两个排序列表

在这里插入图片描述

这个题我们可以同时遍历两个链表,然后拿小的下来尾插,思想差不多就是这样,我们需要一个头,但是这个头可以是哨兵位,也可以是空,我们这里写两个版本的。

带哨兵位的
代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
	//如果一个链表为空直接返回另一个链表
    if(list1==NULL) return list2;
    if(list2==NULL) return list1;
    //创建哨兵位
    struct ListNode* newhead = NULL,*tail = NULL;
    newhead = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
    while(list1&&list2)
    {
        if(list1->val<list2->val)
        {
        //不用考虑头结点为空的情况直接尾插就可以
            tail->next = list1;
            tail = tail->next;
            list1= list1->next;
        }
        else
        {
        //不用考虑头结点为空的情况直接尾插就可以
            tail->next = list2;
            tail = tail->next;
            list2= list2->next;
        }
    }
    //如果一个链表为空,直接将另一个链表链接过来
    if(list1==NULL) tail->next = list2;
    if(list2==NULL) tail->next = list1;
    //保返回的头
    struct ListNode* next = newhead->next;
    //释放哨兵位
    free(newhead);
    return next;
}

不带哨兵位
代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    //如果一个链表为空直接返回另一个链表
    if(list1==NULL) return list2;
    if(list2==NULL) return list1;
    struct ListNode* newhead = NULL,*tail = NULL;
    while(list1&&list2)
    {
        if(list1->val<list2->val)
        {
            //判断头结点是否为空
            if(newhead==NULL)
            {
                newhead = tail = list1;
                list1 = list1->next;
            }
            else
            {
                tail->next = list1;
                tail = tail->next;
                list1= list1->next;
            }
        }
        else
        {
            //判断头结点是否为空
            if(newhead==NULL)
            {
                newhead = tail = list2;
                list2 = list2->next;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
                list2= list2->next;
            }
            
        }
    }
    //如果一个链表为空,直接将另一个链表链接过来
    if(list1==NULL) tail->next = list2;
    if(list2==NULL) tail->next = list1;
    return newhead;
}

今天的分享就到这里了,感谢大家的支持和关注。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不是笨小孩i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值