链表OJ题第二弹:环形链表和环形链表 II

前言

第一弹的链表题目比较基础,下面两道题目难度升级,可以先自己挑战一下,再来看解析。解析有图示和的文字,有助于你的理解。


1. 环形链表

(1)题目及示例

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。

示例1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环

(2)解法

在环形链表中,从头结点开始出发,会进入到一个循环,不会停下来。我们可以用快慢指针来解决这个问题,慢指针走一步,快指针走两步。

如下图所示,因为它们两个指针没走一次,之间的距离就减小一步,并且它们之间的距离是整数一是任何整数的因子,所以fast指针一定能追上slow指针。

如何写成代码呢?我们可以写一个while循环,每次slow指针走一步,fast指针走两步,但是判断条件是什么?应该是该链表不是环形链表的情况。

  • 当有偶数个结点时,fast指针会直接到空指针,
  • 如果是奇数个结点的话,fast指针会走到空指针的前一个结点,再走的话就没跨过空指针。

所以判断的条件就是fast或者fast的next指针不为空。

bool hasCycle(struct ListNode *head) 
{
    struct ListNode* slow = head, *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            return true;
        }  
    }
    return false;
}

(3)深入思考

我们上面已经证明了快指针走两步是一定可以追上的,那快指针一次走3步,走4步,一直到n步行吗?下面我将分析fast指针一次走3步的情况:

当N是奇数时,我们假设环形链表有C个结点。

所以会有一次追上了,两次追上了,还有永远追不上的情况。如果是fast指针一次走四步,也可以这么分析,只不过情况变复杂了。

结论:fast指针一次走大于两步,不一定追得上slow指针。

2.环形链表II

(1)题目及示例

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。

示例1:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例2:

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例3:

输入:head = [1], pos = -1
输出:返回null
解释:链表中没有环

(2)思路详解

在阐述解题思路之前,我们先想一下:

  1. 当slow指针进入环形链表的时候,fast指针在环中能走几圈?
  2. 当slow指针进入环形链表的时候,fast指针需要走几圈,它们才能相遇?

第一个问题,从头结点到环形链表的第一个结点的长度,和环形链表的长度是随机的,所以可能出现slow指针还没进入环形链表时,fast指针在环形链表中已走了一圈以上的情况。

第二个问题,当slow指针进入环形链表中,不管fast指针走了几圈,基本都在slow指针的前面。我们想一想,slow指针走完一圈,fast指针至少走两圈以上,并且fast指针还在slow指针的前面,所以slow指针在走第一圈的时候,一定被追上。

如上图所示,我们知道了slow指针和fast指针走过的距离,并且我们知道slow指针一次走一步,fast指针一次走两步,就有了这样的等式关系:

2(L+X) = L+X+nC (n >=1)

化简后得到:L = nC - X (n = 1,2,3,4……)

我们假设n = 1,于是就有了L = C - X。也就是说,两个指针分别从头结点和相遇结点开始,一次走一步,当他们两个指向的地址相同时,这就是环形链表的第一个结点。那n不是1呢?结论也成立,只不过从相遇结点开始的指针需要再环中走上一圈以上。代码如下:

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* slow = head, *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            //相遇
            struct ListNode* meet = slow;
            //利用公式
            while(meet != head)
            {
                meet = meet->next;
                head = head->next;
            }
            return meet;
        }
    }
    return NULL;
}


总结

这两道题做下来,会开拓自身的思路,可以自己推导一下上面的证明,巩固所学。话不多说,练起来!

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值