链表篇——环形链表[Leetcode142,判断是否有环,寻找环的入口]{最详细的分析版本}

题目:Leetcode142:环形链表

给定一个链表的头节点  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
解释:链表中没有环。

完整代码:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* q=head,*s=head;//设置快慢指针
        while(q&&q->next)//循环条件是快指针及其后继结点不为空
        {
            s=s->next;
            q=q->next->next;
            if(s==q) break; //当快慢指针相遇,说明存在环状结构,跳出循环
        }
        //如果快指针挂空或快指针后继结点挂空,说明不存在环状结构,直接返回空指针
        if(!q||!q->next) return nullptr;
        //否则开始寻找起始结点,设置两个指针
        ListNode* index_1=head;
        ListNode* index_2=s;
        //当两个指针相遇时,所在结点位置即环的入口结点
        while(index_1!=index_2)
        {
            index_1=index_1->next;
            index_2=index_2->next;
        }
        return index_1;
    }
};

解题思路:

首先思考怎么判断链表中是否存在环形结构,环形结构的特征是从环中的某一结点出发,最终会返回到自身结点,但直接通过遍历来搜索是否有满足条件的结点很难,于是想到了屡试不爽的双指针法 

使用一个快指针和一个慢指针,当出现环状结构时,两者一定会在环中相遇,而且一定是快指针多转了几圈后赶上慢指针;而不存在环状结构时快指针一定会最终挂空,于是我们找到了判断环状结构的思路:

设置一个一次移动两次的快指针和一个一次移动一次的慢指针往前走,看是否相遇,实现代码如下:

        ListNode* q=head,*s=head;//设置快慢指针
        while(q&&q->next)//循环条件是快指针及其后继结点不为空
        {
            s=s->next;
            q=q->next->next;
            if(s==q) break; //当快慢指针相遇,说明存在环状结构,跳出循环
        }
        //如果快指针挂空或者其后继结点挂空,说明不存在环状结构,直接返回空指针
        if(!q||!q->next) return nullptr;

接下来难点是如何判断环的入口,需要一定的数学计算,我们先画个图看看

 首先我们先证明快指针一定在慢指针未走完一圈时与其相遇:

 慢指针进入入口的路程是x,快指针此时的路程是2x。

设一圈长度为C(C=y+z),若绕一圈后,其路程为x+C,则可知快指针此时的路程为2(x+C),可以知道这段时间内快指针绕了两圈,那它绕第一圈的时候,慢指针必定没走完第一圈,故快指针一定会与慢指针相遇

然后我们就可以放心的列出相遇问题的表达式:

\frac{x+y}{1}=\frac{x+y+nC}{2}

简单整理一下得到

2(x+y)=x+y+n(y+z)\rightarrow{x=n(y+z)-y}=(n-1)(y+z)+z

观察到y+z=C,于是

x=kC+z(k\in Z)

这里的x就是我们要找的入口位置,z是相遇结点和入口结点的距离,我们惊讶的发现,正好相差环周长的整数倍!!!

于是我们找到了判断入口的方法:设置一号指针从表头出发,二号指针从相遇结点出发,速度相同,那当一号指针到达入口位置时,二号指针走的路程就是z加上整数倍的周长,同样一定到达入口位置!因此,两个指针相遇位置就是入口位置。

实现代码如下:


        //否则开始寻找起始结点,设置两个指针
        ListNode* index_1=head;
        ListNode* index_2=s;
        //当两个指针相遇时,所在结点位置即环的入口结点
        while(index_1!=index_2)
        {
            index_1=index_1->next;
            index_2=index_2->next;
        }
        return index_1;
   

就此,我们已经顺利解决本题

算法复杂度分析:

空间复杂度: 只使用了四个指针,空间复杂度当然是O(1)

时间复杂度:这里分两个阶段考虑时间复杂度,第一个阶段慢指针移动的距离不超过链表长度,第二个阶段一号指针移动的距离也不超过链表长度,由于每一个移动距离对应一次执行语句,所以时间复杂度为:O(n)+O(n)=O(n)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值