前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >环形链表问题(判环+寻找入环点)

环形链表问题(判环+寻找入环点)

作者头像
YIN_尹
发布2024-04-10 10:10:24
680
发布2024-04-10 10:10:24
举报
文章被收录于专栏:YIN_尹的博客YIN_尹的博客

题目1.判断链表中是否有环

链接: link

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true 。 否则,返回 false 。

1.1 思路分析(快慢指针)

什么是环形链表呢?

其实就是链表的尾结点的next不指向NULL,而是指向链表中的任意一个结点,也可以是自己。

我们来简化一下环形链表的图,其实就是长这样:

那这道题单纯的要判断链表中是否存在环其实很简单,思路就可以用我们熟悉的快慢指针:

定义两个指针fast和slow,初始都指向链表的第一个结点。然后快指针fast一次走两步,慢指针slow一次走一步。 如果链表中存在环,那他们就一定会相遇,即fast==slow。 如果链表中没有环,那么fast指针一定会率先走到空,因为fast是一次走两步,所以要考虑链表的可能是奇数个也可能是偶数个。 如果是奇数个循环结束条件是fast->next==NULL 偶数个的话就是fast==NULL时结束

那我们来写一下代码:

就过啦!

代码语言:javascript
复制
bool hasCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    fast=slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
            return true;
    }
    return false;
}

代码呢确实很简单,但是,还有一些问题值得我们来思考一下

1.2 思考:为什么快指针每次走两步,慢指针每次走一步两者一定可以相遇?

大家有没有想过为什么快指针每次走两步,慢指针每次走一步在带环的情况下两者一定可以相遇呢?

我们来一起证明一下:

慢指针slow一次走一步,我们假设慢指针进环的时候,fast和slow的距离是N

那此时fast和slow两个人都在环里,两者距离为N,而fast又比slow走得快,所以fast是不是就有可能追上slow。

那为什么fast一次走两步,slow一次走一步就一定可以追上呢?两者就一定会相遇呢?有没有可能会错过呢?

🆗,这样是一定可以相遇的: 此时两者都在环里,距离为N,fast一次走两步,slow一次走一步,所以它们的速度差是1。 也就是说,往后每走一次,两者的距离就缩小1 N,N-1,N-2,... ,3,2,1,0 那么N次之后,两者的距离就会缩小到0,此时两者就相遇了。

而且肯定在一圈之内就追上了,因为慢指针入环的时候,两者的距离肯定是小于环的周长的。

1.3 快指针一次走3步,走4步,…n步行吗?

那我们再来思考,上面我们证明了慢指针一次走一步,快指针一次走两步一定可以相遇。那么,快指针一次多走几步还可以吗?走3步,走4步,…n步行吗?

那这样的话能不能相遇就要看情况了,我们来分析一下,比如,我们以快指针每次走3步来分析一下(其它情况也类似):

慢指针slow呢还是一次走一步,那我们还是假设当slow走到入环点的时候,两者距离为N slow进入环之后呢,fast就开始追击slow了。 那么此时fast一次走3步,slow一次走1步,即它们的速度差是2,也就是说,每追击一次,两者的距离缩小2 那此时它们还一定会相遇吗? 🆗,此时就要分情况看了: 如果N是偶数,那么N每次-2,最终一定可以减小到0,那就可以相遇。 如果N是奇数,每次-2,最终会减到...3,1,-1 那当它们的距离N变成-1的时候,意味着什么? 两者是不是错过了啊。fast直接跳到了slow前面距离为1的位置。 那此时两者的距离又变成了多少?

如果假设环的周长是C,那他们的距离就变成了C-1,然后fast重新开始追击slow,那这次能相遇吗? 是不是又取决于它们的新距离C-1是奇数还是偶数啊? 每次追击距离缩小2,如果C-1是偶数可以相遇如果C-1是奇数那么将永远追不上了! 因为C-1是奇数的话,最终又会减到-1,减到-1的话它们的距离就还是C-1,C-1是奇数,最终又会减到-1,减到-1的话它们的距离就还是C-1… 就会一直循环下去,永远追不上!!!

而C-1到底是奇数还是偶数,我们不知道,这取决与环的大小。

那如果每次fast走的更多,走4步,5步,…n步也是一样的:

就看它们在对应的速度差下距离能不能缩小到0,slow入环时距离为N,假设速度差是gap,N每次减去gap,如果最终可以减到0,就可以相遇(即看N是不是gap的整数倍)。 如果最终不能减到0,那他们就会错过,假设错过之后距离为C-X,如果C-X是速度差gap的整数倍,那还可以相遇,如果不是,那就永远不能相遇。

所以:

如果快慢指针的速度差是1,那么一定可以追上相遇,如果大于1,就不一定了。

题目2. 寻找入环点

那么下面我们再来看一道环形链表的题目 链接: link

这道题呢,我们不仅要判断链表有没有环,还要返回入环的结点,如果链表无环,则返回 null。

2.1 思路1

这道题单要写代码的话呢其实很简单,有一个方法是这样的:

上面我们刚做了一道题不是判断链表是否带环嘛,用快慢指针如果最终可以相遇的话就是有环。 那现在要寻找入环点,就可以这样: 让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终就一定会在入环点相遇。

2.2 代码实现

那我们来写一下代码,看看能不能通过:

🆗,就过啦!

代码语言:javascript
复制
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;
        if(fast==slow)
        {
            struct ListNode *meet=fast;
            struct ListNode *begin=head;
            while(meet!=begin)
            {
                begin=begin->next;
                meet=meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

但是,为什么一个指针从判环的相遇点开始走,一个指针从链表起始位置走,就一定会在入环点相遇呢?

那下面我们来证明一下这个结论

2.3 证明:为什么一个指针从相遇点开始走,一个指针从链表起始位置走,两者会在入环点相遇?

那我们依然还是来画图分析一下:

我们假设链表起点到入环点的距离为L,入环点到相遇点的距离为N,那相遇点在往前走到入环点的距离就是C-N。 那么快慢指针在相遇的时候,所走的路程: 慢指针slow:L+N ps:慢指针在环内走的距离不会超过一圈的,上一题我们分析了,慢指针入环时两者的距离肯定小于N,一圈之内就追上了。 快指针fast:L+k*C+N 解释:快慢指针相遇时,快指针fast已经绕环走了k圈了,k至少为1。因为fast先进入环,而且速度快,所以一定先独自经过相遇点M,而最终两者又在M相遇。所以fast至少绕环走了一整圈再+N走到相遇点。 即k至少为1,至于具体的大小还取决于环的大小,环长C相对于L越小,k就越大。

然后:

又因为快指针的速度是慢指针的2倍,所以: 相遇时快指针的路程是慢指针的2倍,即 L+k*C+N=2*(L+N) k*C=L+N 所以: L=k*C-N,即: L=(k-1)*C+C-N

那我们再来看图: L=(k-1)*C+C-N,然后我们上面的思路不是让两个指针分别从起点和相遇点开始走嘛

begin从起点开始走,meet从相遇点开始走,两人同步走,每次都是走一步。 那begin走了L步走到入环点 meet就也走L步,L又等于(k-1)*C+C-N,即meet先绕环走k-1圈(k>=1),那meet从入环点开始走的,不论走几圈,只要是整圈,还停下来就还是在相遇点这个位置嘛,然后还要走一个C-N,而我们看图C-N刚好就是相遇点距离入环点的距离。 所以meet走了L((k-1)*C+C-N)步之后正好也走到了入环点

那么就得以证明:

一个指针从判环的相遇点开始走,一个指针从链表起始位置走(每次都走一步),两者正好会在入环点相遇。

2.4 思路2(转换为链表相交问题)

那么这道题呢我们再来提供另外一种解法:

就是把它转换成链表相交的问题,我们前面写过这道题——链接: link

怎么做呢?

首先还需要找到快慢指针的相遇点,然后从相遇点把环形链表断开——变成单链表

然后就变成了相交链表找交点的问题

2.5 代码实现

我们来写一下代码:

相交链表找交点的代码我就不写了,我们直接拷贝之前写的:

然后我们只需:

提交

过啦!

代码语言:javascript
复制
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *curA=headA;
    struct ListNode *curB=headB;
    int lenA=0;
    int lenB=0;
    //找尾,先判断是否相交,不相交直接返回NULL,相交再找
    while(curA->next)
    {
        lenA++;
        curA=curA->next;
    }
    //计算准确长度应该这样写:while(curA)
    //这样while(curA->next)比实际长度小1,但是两个都小1,不影响差值
    //而这样写循环结束cur就是尾,可直接判断是否相交
    while(curB->next)
    {
        lenB++;
        curB=curB->next;
    }
    //尾不相等,则不相交
    if(curA!=curB)
        return NULL;
    //计算差值
    int gap=abs(lenA-lenB);
    //找出长的那一个
    struct ListNode *longlist=headA;
    struct ListNode *shortlist=headB;
    if(lenB>lenA)
    {
        longlist=headB;
        shortlist=headA;
    }
    //长表先走差值步
    while(gap--)
    {
        longlist=longlist->next;
    }
    //再一起走,比较找交点
    while(longlist!=shortlist)
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return longlist;
}

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;
        if(fast==slow)
        {
            struct ListNode *otherhead=fast->next;
            fast->next=NULL;
            return getIntersectionNode(head,otherhead);
        }
    }
    return NULL;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目1.判断链表中是否有环
    • 1.1 思路分析(快慢指针)
      • 1.2 思考:为什么快指针每次走两步,慢指针每次走一步两者一定可以相遇?
        • 1.3 快指针一次走3步,走4步,…n步行吗?
        • 题目2. 寻找入环点
          • 2.1 思路1
            • 2.2 代码实现
              • 2.3 证明:为什么一个指针从相遇点开始走,一个指针从链表起始位置走,两者会在入环点相遇?
                • 2.4 思路2(转换为链表相交问题)
                  • 2.5 代码实现
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档