链表经典刷题--快慢指针与双指针

文章介绍了使用快慢指针解决链表问题的技巧,包括如何找到链表的中间结点、倒数第k个节点以及如何在单链表中删除指定元素。快慢指针的思想在于通过不同步速的指针遍历链表,实现特定的查找和操作,如在只遍历一次链表的情况下确定链表特性。

本篇总结链表解题思路----快慢指针,其实也就是双指针,这个快慢并不单纯指“快慢”,它更多的可以表示,速度快慢,距离长度,时间大小等等,用法很有趣也很独特,理解它的思想,是很有必要的。

🍭1.链表的中间结点----‘速度’

加粗样式
要求返回链表中的中间结点;
当有奇数个结点时,返回中间的结点,当有偶数个结点时,返回第二个中间结点。
传统的思想是遍历链表,算链表长度,然后再找中间值。
但如果只允许遍历一遍链表呢?这种方法就不好使了。

使用快慢指针,轻松解决。
什么叫快慢指针,就是让两个指针都指向链表同时往后走,一个指针每次走两步,称为快指针,一个指针每次走一步,称为慢指针。
当快指针走到尾时,而慢指针所指向的地方就是中间结点。
这种天才想法在后面的许多链表题中都有应用,对解题很棒。
本题需要注意一下奇数个和偶数个结点有什么不同,当结点为偶数时,fast走到了最后一个结点的后面,所以fast成为NULL,当结点为奇数时,fast走到最后一个结点停止,所以fast->next为NULL。所以这个循环的条件就是fast和fast->NULL都不为空时,快慢指针才能往后走。
在这里插入图片描述

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head)
{
   struct ListNode *fast,*slow;//设置两个指针
   fast=slow=head;//让快慢指针同时从开头走
   while(fast!=NULL&&fast->next!=NULL)//当fast不为NULL和fast->next不为空时,才可以正常走,一旦有一个为NULL,则说明两种情况中有一个完成。
   {
       slow=slow->next;//慢指针每次走一步
       fast=fast->next->next;//快指针每次走两步
   }
   return slow;//最后不管结点是偶数个还是奇数个,返回值都是慢指针指向的位置

}

在这里插入图片描述

🍭2.链表中倒数第k个结点----‘距离’

在这里插入图片描述
先输入k,要求输出该链表的第k的位置上的结点。
传统思想,遍历链表,算出长度,然后用长度减k再加1即可。
那如果跟上面一样只给你遍历一遍链表那该怎么做呢?

同样方法,快慢指针
只不过这时比较的不是速度了,而是两个指针之间的距离。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以让快指针先走k-1步,然后再让慢指针开始走,当快指针走到最后一个结点时停止,慢指针指向的就是要输出的。
在这里插入图片描述

我们还可以让快指针先走k步,然后让慢指针开始走,这时,快慢指针之间的距离就变成了k,当快指针走到最后一个结点时,慢指针其实指向的是前面的结点,而要让慢指针指向后面的结点,让快指针再走一步就可以了。
代码如下:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode *fast,*slow;//定义两个指针
    fast=slow=pListHead;//一开始快慢指针都指向开头
    if(fast==NULL)//如果链表为空链表则无法返回,返回NULL
    return NULL;
    int num=k-1;
    if(k==0)//如果是倒数0个那么也返回NULL
    {
        return NULL;
    }
    while(num--)//先让快指针走k-1步
    {
        if(fast->next==NULL)//我们要确保k的大小不能大于链表的长度对吧,当k的长度大于链表时,就会让链表访问越界,所以先给fast往后走时,要注意不能越界了,如果fast->next为NULL,则表明k是大于链表长度的,之间返回NULL
       {
         return NULL;
       }
        fast=fast->next;
    }
    while(fast->next)//判断结束的条件是当fast->next为NULL时,结束,也就是fast走到最后一个结点时停止,这时快慢指针之间的距离为k-1,而最好一个结点与慢指针所指向的结点的距离就是k-1
    {
        slow=slow->next;//慢指针和快指针一起走
        fast=fast->next;
    }
    return slow;//最后返回slow位置即可
}

在这里插入图片描述
第二种图解:
在这里插入图片描述

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode *fast,*slow;//定义两个指针
    fast=slow=pListHead;
    if(fast==NULL)//当链表为空时,返回NULL
    return NULL;
    while(k--)//快指针先走k步
    {
        //要考虑k如果大于链表本身长度时就非法
        if(fast==NULL)
        {
            return NULL;
        }
        fast=fast->next;//
    }
    while(fast)//这个和快指针先走k-1的结束条件不一样喔,这个结束条件应该是快指针走到最后结点的后面,所以是fast为NULL循环结束
    {
        slow=slow->next;//快慢指针一起走
        fast=fast->next;
    }
    return slow;//返回慢指针所在的位置
}

🍭3.移除链表元素

在这里插入图片描述
方法1:
我们可以按照传统单链表是如何删除来写。
单链表如何删除呢?
我们要删除这个结点,还要将前面的结点和后面的结点链接起来。这时我们就必须要记录下前面结点的地址了,不然单链表无法再找到前一个结点。
所以我们可以用双指针,一个是进行遍历的指针cur,另一个是用来记录它前面结点的位置的指针prev。
当cur->val!=val时,cur先赋值给prev记录这个位置,然后cur再往后走。
当cur->cal==val时,那么就要将前一个结点链接到后一个结点上。
pre->next=cur->next;
然后释放掉cur。
大体思路是这样,不过还有一些细节问题。
比如如果最后一个元素是要被删的,那么前面一个结点的next就变成野指针了。
所以我们需要将他置为NULL。
还有个问题,如果第一个就是要被删除的结点,那么prev->next就非法,所以要进行讨论
当prev= =NULL时,说明第一个结点就要被删除,相当于头删。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val){

struct ListNode*cur=head;//cur用来遍历链表,首先指向最前面的
struct ListNode*prev=NULL;//一开始prev为空
while(cur)//利用cur进行遍历
{
    if(cur->val!=val)//如果不等于
    {
        prev=cur;//让prev记录这个位置
        cur=cur->next;//cur往后走
    }
    else//如果等于
    {
        if(prev==NULL)//要考虑是否是第一个要删除的,当prev==NULL时,就是删除第一个结点
        {
            head=cur->next;//将头结点与第二个结点链接
            free(cur);//是否cur
            cur=head;//cur往后走,也就是走到第二个结点位置
        }
       else
       {
        prev->next=cur->next;//将前面的与后面的链接
        free(cur);//删除中间的
        cur=prev->next;//cur往后走
       }
    }
}
return head;//返回头结点
}

方法2:
我们可以利用数组删除元素的思想来删除链表中的结点
在数组中我们是将不等于val的元素放入一个新的数组。
而在链表里我们也可以这样,当不等于val的结点我们就插入到一个新的链表上去,所以我们还需要创建一个新链表。

我们每次将新结点插入到新链表中时都需要找尾,非常烦人,我们可以定义一个尾指针tail,用来指向新插入结点的地址。
如果cur->val!=val那么就进行尾插,将新结点插入新链表
如果cur->val=val,那么cur 就往后走。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val){

struct ListNode*cur=head;
struct ListNode*tail=NULL,*newnode=NULL;//创建一个新结点
while(cur)//遍历链表
{
    if(cur->val!=val)//如果不相同,就把不相同的结点插入到新的链表中
    {
        //尾插
        if(newnode==NULL)//最初新链表肯定为空,可以直接将cur指向的不相同的结点放入到新链表中
        {
            newnode=tail=cur;
        }
        else
        {
            tail->next=cur;//cur所对应的结点插入到tail后面
            tail=tail->next;//tial要往后走,记录下一个插入结点的位置
        }
        cur=cur->next;//cur也要往后走
    }
    else
    {
        struct ListNode*next=cur->next;//如果元素相同,那先把这个结点的下一个结点地址记录下来
        free(cur);//释放这个要删除的结点
        cur=next;//cur往后走
    }
}
   if(tail!=NULL)//当tail不为空时,我们需要将tail的next置为NULL
        tail->next=NULL;
return newnode;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tew_gogogo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值