含单环链表的追击问题

 原题如下——

. - 力扣(LeetCode)

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

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

不允许修改 链表。

思路————快慢指针 + 数量关系

题目的本质是

1.判断是否有环

2.若有环,找到入环节点

1.判断是否有环

判断环,这里用到了环的性质,即环没有终点。

若有一快一慢指针运动(暂不考虑离散性),则两指针必然相遇;若无环,则快指针将到达终点(尾节点或NULL)

其次要考虑快指针和慢指针的运动速度

不妨先设快指针一次移动2个节点,慢指针移动1个节点。

若有环,当快慢指针都进入环内,两者速度差为1,若慢指针入环时两者相距C个节点,易知总共循环C次,两者相遇

struct ListNode 
{
    int val;
    struct ListNode *next;
};//链表基本特征

struct ListNode *detectCycle(struct ListNode *head) 
{
    if(!head || !head->next)
        return NULL;
    struct ListNode* slow = head, * fast = head, * begin = head;
    slow = slow->next;
    fast = fast->next->next;
    while(fast != slow && fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    /*
    if(fast != slow)
        return NULL;  
    while(begin != slow)
    {
        begin = begin->next;
        slow = slow->next;
    }
    return begin;    
    */
}

问题来了,既然快指针速度为2、慢指针速度为1可以,那么快指针速度为3、4......能否判断是否有环?

再举快指针速度为3的例子:

假设有环,慢指针走到入环节点时:

(吐槽一下,win11的画图真心用不习惯~  建议用win10的画图)

快指针走到了距离入环节点为C的位置,

快慢指针路程的数量关系如下(n为快指针此时走过的环数)——

L_{slow} = L

L_{fast} = L+nR+(R-C)

L_{fast}=3L_{slow}

解得参数的数量关系为   C=(n+1)R-2L

此时快慢指针距离为  L_{relate} = C

在追击中,两者距离L_{relate}

C-2

C-4

C-6

......

这里要进行分类讨论:

第一种:C为偶数,两者距离逐渐变为

6

4

2

0

当两者距离为0时,两者相遇;

第二种:C为奇数时,两者距离逐渐变为

7

5

3

1   

此时慢指针领先快指针1个节点,下一个小循环,快指针向前走3个节点,慢指针走1个节点,快指针、慢指针发生交错不相遇,并领先1个节点,两者进入下一个追击——

此时L_{relate} = R-1

这里再次分类讨论:
R为奇数时,R-1为偶数,变为第一种情况,两者可相遇;

R为偶数时,R-1为奇数,变为第二种情况,两者再次形成L_{relate}=R-1的追击,不能相遇

所以这种情况下若有环,R为奇数则两者必相遇,C不为奇数R为偶数则相遇,C为奇数R为偶数则永远不相遇,那么R为偶数、C为奇数能否成立?

上面我们已经得出    C=(n+1)R-2L,当R为偶数时,根据运算性质可知C必然为偶数,也就是R为偶数、C为奇数无法成立,根本没有这种情况

综上,快指针速度为3个节点,慢指针为1个节点,依旧可以判断链表是否含有环。

一般情形下,当快指针速度为M,慢指针速度为N(M>N),仍可按上述思想处理。

2.寻找入环节点

由上述知,若链表存在环 则快慢指针相遇. 就快指针速度为2个节点,慢指针速度为1个节点来讨论

如图,两指针在环内相遇时,两者顺时针正向方向距离入环节点为d,根据路程的数量关系有

L_{slow}=L+NR+(R-d)   

L_{fast}=L+MR+(R-d)

L_{fast}=2L_{slow}

(N为慢指针走过的环数,M为快指针走过的环数)

在这里N有隐藏的数量特征,即N=0

直觉想象一下,当在环内快慢指针开始追击时,快指针会在一圈内追上慢指针,

当然也可以使用假设证明:

假设  N\neq 0,慢指针刚入环时快慢指针距离L_{relate}=C

易知经历C次过程后快指针追上慢指针,此时慢指针在环内走过的路程为\Delta L_{slow}=C<R,未走完一环,即N=0证明完毕

化简上述3式得

得到    L=(M-1)R+d

若想要找到入环节点,只需在链表起始位置设置一指针begin,使其速度与slow指针相同,

当指针begin经过路程L,指针slow遍历M-1次环并与begin相遇,此时记为入环节点

struct ListNode 
{
    int val;
    struct ListNode *next;
};//链表基本特征

struct ListNode *detectCycle(struct ListNode *head) 
{
    //if(!head || !head->next)
    //    return NULL;
    struct ListNode* slow = head, * fast = head, * begin = head;
    /*
    slow = slow->next;
    fast = fast->next->next;
    while(fast != slow && fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    if(fast != slow)
        return NULL; 
    */ 
    while(begin != slow)
    {
        begin = begin->next;
        slow = slow->next;
    }
    return begin;    
}

完整代码如下

struct ListNode 
{
    int val;
    struct ListNode *next;
};//链表基本特征

struct ListNode *detectCycle(struct ListNode *head) 
{
    if(!head || !head->next)
        return NULL;
    struct ListNode* slow = head, * fast = head, * begin = head;
    slow = slow->next;
    fast = fast->next->next;
    while(fast != slow && fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    if(fast != slow)
        return NULL;  
    while(begin != slow)
    {
        begin = begin->next;
        slow = slow->next;
    }
    return begin; 
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值