链表经典题之环形链表

        通过我们学习了数据结构——链表的知识后。我们需要对链表有一定更深的认识。所以说,刷题是必须的。下面,就让我们来看看有关链表的经典题型——环形链表吧。

环形链表I

题目描述:

        首先,我们来看一看题目,通过题目的描述,我们可以知道,题目给我们一个链表的头结点,让我们去判断这个链表是否存在有环。

        从示例1中我们可以看到,-4这个节点其实是应该指向空的,它却指向了2这个节点,从进入2这个节点后,就出现了一个环,所以说这个链表就是一个存在环的结构。

示例2也很好看出,值为2的这个节点,它指向了首节点值为1的节点,它也是一个环形结构。

        再看一看示例3,它只有一个节点,也就是说该节点的下一个节点为空,它就不可能出现环形结构,至少得两个节点才能形成题目中所需要的环形结构。

思路分析:

        当我第一次看到这个题目的时候,我的脑子也一片空白,无从下手,不知道该怎么做。其实,仔细想一想,它还是有迹可寻。首先,我们要知道一个链表是否有环,我们就可以先从环入手,我们可以把环想想成我们的操场,我们在跑1000米比赛的时候,有的同学可能体力好,训练过,跑的很快,有的同学则缺乏锻炼,跑的气喘嘘嘘。以致于最后被人家超圈了。对!就是超圈了的时候,是不是证明你们是在一个圈或者说是环里面跑呢。

        所以说,当我们需要判断是否存在环的时候,我们就定义两个指针,一个指针跑得快,一次可以跑两步,一个指针跑得慢,一次只能跑一步,当快指针和慢指针相等的到时候,它们是不是就相遇了呢,就证明它们是在一个环里面跑。如果快指针跑到了空的地方去,是不是就是不存在环的情况呢。

        上面这种方法也叫快慢指针法,在链表这一章节的很多题中都能得以体现。

代码实现

  
//定义链表节点
struct ListNode {
      int val;
      struct ListNode *next;
 };
 
bool hasCycle(struct ListNode *head) 
{   
    //如果head为空或者head->next为空,返回false
    if(head==NULL||head->next==NULL)
        return false;

    //让fast一次走两步,slow一次走一步,如果能相遇,就存在环
    //如果fast走到了空,则没有环
    struct ListNode*fast=head;
    struct ListNode*slow=head;
    while(fast!=NULL&&fast->next!=NULL)
    {   
        
        fast=fast->next->next;
        slow=slow->next;
         if(fast==slow)
            return true; 
        
    }

环形链表II

题目描述:

        下面这道题,就是我们上一道题的升级,我们不仅需要知道题目所给的链表是否为环,而且还要返回该链表进环时的入口。如果不存在环,则返回NULL,否则返回入口节点。

        通过前面的讲解,我们知道了值为-4这个节点指向了值为2的这个节点,所以说值为2的这个节点就是环的入口节点,我们就需要找到它的地址并返回它。

示例2中,我们可以看出值为1的这个节点就是我们需要找的节点。

和上面那道题一样,这个示例它不是一个环形结构,它就根本找不到入口点,所以说需要返回NULL。

思路分析:

        很多同学,拿到这道题也是什么思路也没有。我自己看到这道题的时候,脑子里面也是懵的,根本不知道该如何下手。其实,有些时候,也跟我们刷题少有关,我们需要敲大量的代码,做过大量的题,才能有一定的解题基础。这道题的关键点就是我们根本不知道如何才能找到这个环的入口。

        其实,在这里有一些前辈已经给我们找到了一些规律。就是当存在环的时候,快慢指针一定会在环里相遇,这个时候我们只需要将头指针也向前走,当头指针再一次和慢指针相遇的时候,这时候的交点就是我们的环的入口。

        首先,我们来看上面这幅图,我们首先设头指针到环入口的距离为L,设环的大小为C,然后slow,fast的相遇点距离环的入口的距离为X。首先我们可以知道slow的速度是fast的一半,也就是说slow指针必须在它走一圈环的距离时它和fast指针就会相遇,也就是说X<C,也就是说slow指针如果走了一圈了,fast指针肯定走了两圈了他们肯定相遇了,所以它们肯定会再slow指针走完一圈环的距离时相遇。

        然后我们再来看slow走过的距离是L+X,而fast走过的距离则是L+X+C+(n-1)*C。我们由fast走过的距离是slow的两倍可以算出 2(L+X)=L+X+C+(n-1)*C。如果说环很小,而L的距离很大的时候,fast指针就可能再环中走过了很多圈了。我们把他们解出来,就可以得到L+X=C。所以说C-X的距离就是L,而头指针距离环入口的距离也是L。那么此时我们只需要将slow指针和head指针,从fast和slow相遇的时候开始走,那它们一定会再环入口的地方相遇。

        我们已经分析完了,下面就开始写代码吧。

代码实现:
 

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

struct ListNode *detectCycle(struct ListNode *head) 
{   
    //首先定义快慢指针
    struct ListNode*fast=head;
    struct ListNode*slow=head;
    
    while(fast&&fast->next)
    {   
        fast=fast->next->next;
        slow=slow->next;    
        //当他们相遇时,就让头指针和slow指针一起走,相遇就是入口
        if(fast==slow)
        {   
            struct ListNode*cur=head;
            while(cur)
            {

                if(cur==slow)
                    return cur;
                      cur=cur->next;
                slow=slow->next;
            }
        }
    }
    //如果不存在环,则返回NULL
    return NULL;
}

总结

        从上面两道链表的题型中我们可以学习到这种快慢指针的方法,它可以通过速度差,来让我们达到我们想要的结果,我们需要控制好两个指针的速度差就可以解决很多问题。

         希望我上面的思路对同学们有所帮助。

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值