C语言之快慢指针

目录

前言

一、快慢指针是什么?

二、快慢指针的应用

1.寻找特殊节点

2.链表的环形问题

总结


 

前言

双指针一般分为两种,一种是使用两个方向相同的指针进行扫描的快慢指针,另外一种是使用两个方向不同的指针进行扫描的对撞指针。而今天,我们要看的是快慢指针。

一、快慢指针是什么?

快慢指针是一种常见的算法技巧,通常定义出两个指针并以不同的速度遍历链表。一般情况下,慢指针每次移动一个节点,快指针每次移动两个节点。用此方法可以解决一些链表中的问题,并且提高了代码效率,简化了时间复杂度。

二、快慢指针的应用

1.寻找特殊节点

    1.简介

  • 运用快慢指针遍历链表,当快指针走到最后时,慢指针则指向所要找的特殊位置。

    2.经典例题

  •  题目

代码如下:

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;
}

时间复杂度为O(L),L为链表的长度 。

  • 题目

 代码如下:

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) 
{    
    struct ListNode*phead=(struct ListNode*)malloc(sizeof(struct ListNode));//创建一个哑节点
    phead->val=0;//初始化哑节点
    phead->next=head;//初始化哑节点
    struct ListNode*n1=phead;//慢指针
    struct ListNode*n2=n1->next;//快指针
    while(n--)//快指针先走n步,使得快慢指针相差n个距离
    {
        n2=n2->next;
    }
    while(n2)//快慢指针一起走,当快指针所指向的节点为空时,慢指针刚好指向要删除节点的前驱节点
    {
        n1=n1->next;//慢指针走一步
        n2=n2->next;//快指针走一步
    }
    struct ListNode*cur=n1->next;//储存要删除的节点
    n1->next=n1->next->next;
    free(cur);//释放掉要删除的节点
    struct ListNode*ans=phead->next;//储存表头
    free(phead);//释放哑节点
    return ans;//返回表头
}

时间复杂度为O(L),L为链表的长度 。

注意:假设不创建哑节点,快慢指针都开始都指向表头,其他不变。程序执行完后,慢指针指向要删除的节点,但是为了删除此节点,我们应该找要删除节点的前驱节点,因为前驱节点存有要删除节点的地址,否则如果直接释放掉要删除的节点,会造成野指针的发生。因此,创建一个哑节点,把慢指针往前移一步,则最终会指向要删除节点的前驱节点。

还有另外一种解法:先翻转链表,然后删除要删除的节点,最后再翻转链表。(此方法思路简单,但是代码量多)

代码如下:

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) 
{
    struct ListNode*cur=head;
    struct ListNode*prev=NULL;
    struct ListNode*next=NULL;
    while(cur)//翻转链表
    {
        next=cur->next;
        cur->next=prev;
        prev=cur;
        cur=next;
    }//最终prev为翻转后新链表的头节点
    struct ListNode*phead=(struct ListNode*)malloc(sizeof(struct ListNode));//创建哑节点
    phead->val=0;//初始化哑节点
    phead->next=prev;//初始化哑节点
    struct ListNode*p1=phead;//p1指向哑节点
    struct ListNode*p2=prev;//p2指向翻转后链表的头结点
    while(--n)//找到要删除链表的前驱节点
    {
        p1=p1->next;
        p2=p2->next;
    }
    struct ListNode*p=p2;
    p1->next=p2->next;
    free(p);//释放要删除的节点
    struct ListNode*cur1=phead->next;
    struct ListNode*prev1=NULL;
    struct ListNode*next1=NULL;
    while(cur1)//再次翻转链表
    {
        next1=cur1->next;
        cur1->next=prev1;
        prev1=cur1;
        cur1=next1;
    }//最终prev1为翻转后新链表的头节点
    free(phead);//释放哑节点
    return prev1;
}

2.链表的环形问题

   1.简介

  • 运用快慢指针遍历链表,利用快指针永远比慢指针快的特性,来判断链表是否有环。如果快慢指针最终相遇,则该链表有环,反之,无环。

    2.经典例题

  • 题目

代码如下:

bool hasCycle(struct ListNode *head) 
{
    if(head==NULL||head->next==NULL)//如果该链表没有节点或者只有一个节点,则该链表一定没有环
    {
        return false;
    }
    struct ListNode *fast=head;
    struct ListNode *slow=head;
    while(fast&&fast->next)//当快指针所指向的节点或下一节点为空时,结束循环
    {
        slow=slow->next;//慢指针每次走一步
        fast=fast->next->next;//快指针每次走两步
        if(fast==slow)//如果有环,则快指针在某一时刻一定会追上慢指针
        {
            return true;
        }
    }
    return false;//反之,则无环
}

时间复杂度为O(L),L为链表的长度。 

  • 题目

  • 思路

设头节点到入环口需要走a步,环长为c。

设快慢指针相遇时,慢指针走了b步,则快指针走了2b步。

设快指针比慢指针多走了n圈,则2b-b=nc,b=nc。

慢指针在环中走了b-a=nc-a圈,因此,在相遇的位置再走a圈就走到入环口了。

同时,头节点开始也走a步,也就到入环口了,即二者同时走,最终会在入环口相遇。

代码如下 :

struct ListNode *detectCycle(struct ListNode *head) 
{
    if(head==NULL||head->next==NULL)
    {
        return NULL;
    }
    int i=1;//判断是否有环
    struct ListNode *fast=head;
    struct ListNode *slow=head;
    struct ListNode *cur=NULL;
    while(fast&&fast->next)
    {
        slow=slow->next;//慢指针一次走一步
        fast=fast->next->next;//快指针一次走两步
        if(fast==slow)//当快指针追上慢指针时,此时快慢指针距离入环口还差a步
        {
            i=0;
            cur=fast;
            break;
        }
    }
    if(i)//判断是否有环,如果没有环,则返回空指针
    {
        return NULL;
    }
    struct ListNode *ph=head;
    while(cur!=ph)//当ph和cur相遇时,即到入环口时,结束循环
    {
        ph=ph->next;//ph从头节点开始走,走a步就到入环口了
        cur=cur->next;//cur从快慢指针相遇开始走,走a步也就到入环口了
    }
    return ph;
}

时间复杂度为O(L),L链表的长度。 


总结

快慢指针在链表这一方面的应用比较多,合理的运用快慢指针,会帮助我们更高效的解决与之相对应的复杂问题,更好的优化我们的程序。

作者第一次在CSDN上写文章,我的基础有限,目前还在学数据结构与算法。文章中难免会有错误的地方,请大家多多包涵,也希望大家能指出错误,作者会一一回应。欢迎大家在评论区里留言,感谢诸位啦~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值