2095. 删除链表的中间节点、19. 删除链表的倒数第 N 个结点、148. 排序链表

1、2095. 删除链表的中间节点

题目描述:
在这里插入图片描述
➡️挑战链接⬅️
分析:
首先题目要求我们删除中间节点,我们知道单链表是单向性的不能回头的,那么当我们找到中间节点时,也应该用一个变量记录一下它的前一个节点,这样便于将删除中间节点过后的两部分节点,连接起来;
那么我们怎么找到中间节点?
当然对于本题来说,头节点是可能变化的,为了避免单独对于头节点的讨论,我们可以定义一个虚拟头节点(哑节点);对于寻找中间节点的位置
我们可以利用快慢指针的办法来寻求帮助:具体办法如下:
我们每次让fast指针走2步,让slow指针走1步:
那么到什么时候停止向前走?
在这里插入图片描述

通过上面图片分析我们可以看出当fast->next==NULL时,我们就可以停止向前走了,这时候slow指针刚好指向中间节点,而我们的prev节点也是刚好指向中间节点的前一个节点;
这时就已经万事俱备了,只需我们去删除掉中间节点了;
为了验证我们上面的条件,我们再来一组数据看看是不是再上面也满足上面的条件;
在这里插入图片描述
我们可以发现这时候上面推出的条件,再这里好像不适用了,其实并不是,我们可以通过对比两幅图可以看到,上一幅图有奇数个节点,下一幅图有偶数个节点;由于奇偶的限制,这就必然导致我虽然每次fast都走两步,slow走一步,但是我停下来的条件不一样;
综上:我们可以得出fast指针停下来的条件:奇数个节点:fast->next==NULL;偶数个节点:fast=NULL
最后不管是以如何方式停下来的,我们的slow都是指向中间节点的,因此接下来,我们对链表进行节点的删除操作都是一样的;
➡️ 代码实现: ⬅️

struct ListNode* deleteMiddle(struct ListNode* head){
     struct ListNode*slow=head;
     struct ListNode*fast=head;
     struct ListNode tmp={0};
     struct ListNode*dummyheda=&tmp;//头指针也有可能改变,设置哑节点
     dummyheda->next=slow;
     struct ListNode*prev=dummyheda;//slow前一个指针
    while(fast&&fast->next)//fast终止条件
    {
        fast=fast->next->next;
        prev=slow;
        slow=slow->next;
    }
     prev->next=slow->next;//删除中间节点
return dummyheda->next;
}

时间复杂度:O(N)
空间复杂度:O(1)
在这里插入图片描述

2、 删除链表的倒数第 N 个结点

题目描述:
在这里插入图片描述

➡️ 挑战链接 ⬅️
分析:
同样这道题也可以用快慢指针来解决,首先题目要求我们要删除倒数第k个节点,那么从另一个方面来说是不是我的slow指针和fast指针之间相差的有k个节点;就好比两个人跑步(直道):甲从起点开始跑,乙从距离起点k米位置开始跑,同时甲乙两人速度大小完全一样,那么是不是说明甲乙两人永远不会相遇,并且甲乙之间的距离会一直保持在k米,不会多也不会少,一直都是这么多,然后呢,结果肯定是乙先跑到终点,当然在乙到达终点时,此时甲还没哟到达终点,并且与乙之间还保持着k米的距离,那么这时候我们是不是就可以说甲在距离终点k米的位置(也就是倒数k米的位置);
然后现在对比回来我们的链表就相当于直跑道,fast就是乙,slow就是甲,终点就是NULL,
当然为了得到甲与乙之间k个距离,我们先让fast指针跑个k个距离,然后再一起跑是不是就是上面例子的思路:
在这里插入图片描述
当然我们定义哑节点的目的就是为了处理头节点被删除的情况,prev指针表示slow的前一个指针;
当然题目保证了k是再合法范围内的,不会超过链表长度,也不会等于负数和0;
➡️ 代码实现: ⬅️


struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    if(!head)//对于空白节点单独处理
    return head;
    struct ListNode*dummyhead=(struct ListNode*)malloc(sizeof(struct ListNode));
    dummyhead->next=head;
    struct ListNode*prev=dummyhead;
    struct ListNode*slow=head;
    struct ListNode*fast=head;
    while(n--)//fast指针先走n步,与slow保持一定距离
    fast=fast->next;
    while(fast)//fast和slow一起以相同速度跑
    {
        fast=fast->next;
        prev=slow;
        slow=slow->next;
    }
    prev->next=slow->next;//删除节点
    return dummyhead->next;
}

时间复杂度:O(N)
空间复杂度:O(1)
在这里插入图片描述

3、排序链表

题目描述:
在这里插入图片描述
➡️ 挑战链接 ⬅️
分析:
题目大概意思就是对链表进行重新排序,我们知道对于数组来说,它拥有很多种排序的方法,这主要是由于其结构决定的,但是对于单链表来说它不能回头,只能往前走,因此对于这样的话许多优秀的排序算法对于链表操作起来难度就更大了;因此呢我们能不能将单链表转换为数组呢?
当然可以,我们可以把单链表的每个节点剪下来,然后存放在一个数组里面,然后再根据节点里面的值(val)来对这个数组进行排序,最后呢再将链表连接起来;这样的思路固然可行,但是对于这道题来说总感觉有一点违背题意的意思;
因此我们可以换个思路,我们可以借用归并排序的思想对于链表进行排序:
首先我们来回忆一下归并排序对于数组是怎么处理的:
首先归并排序的主要思想就是对于有序数组进行选值,先放进临时数组,然后再有临时数组拷贝回原数组:
大概思路:
在这里插入图片描述

换而言之,我们如果相对单链表也按照这样的思想进行排序的话,我们是不是得对有序单链表进行合并(有序单链表的合并

在这里插入图片描述
可是现在我们怎么让链表变为两个有序的子链表?
按照归并的思路,我们可以对链表不断的二分;
最后分到只有一个节点,是不是就可以认为他是有序的了,我们是不是就可以对其进行操作了;
当然合并过后会产生一个新的有序链表,我们又利用这个有序链表继续合并成一个更大的有序链表;
最终链表不就有序了嘛;
➡️ 代码实现: ⬅️

struct ListNode*MergeList(struct ListNode*head)
{
       if(head->next==NULL)//只剩一个节点时停止递归,此时已经有序了
       return head;
       struct ListNode tmp={0};
       struct ListNode*dummyhead=&tmp;
       dummyhead->next=head;
       struct ListNode*prev=dummyhead;
       struct ListNode*slow=head;
       struct ListNode*fast=head;
       while(fast&&fast->next)//寻找中间链表,好把链表分组
       {
           fast=fast->next->next;
           prev=slow;
           slow=slow->next;
       }
       prev->next=NULL;//将2个链表分别单独出来;
       printf("head=%d slow=%d\n",head->val,slow->val);
       struct ListNode*NewHead1=MergeList(dummyhead->next);//head~NULL
       struct ListNode*NewHead2=MergeList(slow);//slow~NULL
       struct ListNode tmp2={0};
       struct ListNode*Newhead0=&tmp2;
       struct ListNode*cur=Newhead0;
       struct ListNode*next=NULL;
       while(NewHead1&&NewHead2)//开始给两个有序链表合并
       {
           if(NewHead1->val<=NewHead2->val)
           {
               next=NewHead1->next;
               cur->next=NewHead1;
               cur=NewHead1;
               NewHead1=next;
           }
           else
           {
               next=NewHead2->next;
               cur->next=NewHead2;
               cur=NewHead2;
               NewHead2=next;
           }
       }
       if(NewHead1)
           cur->next=NewHead1;
       if(NewHead2)
       cur->next=NewHead2;
       return Newhead0->next;
}
struct ListNode* sortList(struct ListNode* head){
  if(!head)
  return head;
  return MergeList(head);
}

在这里插入图片描述

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 33
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南猿北者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值