【算法手记02】环形链表

在之前的题目中,我们接触到的都是无环链表,对于环形链表而言,它任何一个节点的next指针都不指向null,一定会有一个指针的next指向链表中的另一个节点。看题:
142.环形链表 II
image.png

首先,如何判断链表是否有环?常用的方法是快慢指针同时出发,快指针1次走2步,慢指针一次走一步,如果链表有环,快慢指针一定会相遇。这点很好理解,想象一下小学的追及相遇问题,快慢指针入环后,一直在绕圈跑,速度不一致,肯定会有相遇的一刻。

那么,环的入口怎么找呢?

如下图,设入环之前的路程为x,入口点为entry,相遇点为O。入口点到相遇点的距离为y,相遇点返回到入口点的距离为z。
image.png

快慢指针相遇时,它们耗费的时间是一致的,速度关系我们是知道的,那么就可以通过这个来分析路程关系。

S s l o w = x + y S f a s t = x + y + n ( y + z ) \begin{align} &S_{slow} = x + y \\ &S_{fast} = x + y + n(y+z) \end{align} Sslow=x+ySfast=x+y+n(y+z)

快指针要遇到slow,肯定要多跑 n ( n > = 1 ) n(n>=1) n(n>=1)圈。

slow指针一定在一圈之内就被fast追上吗?

是的。不妨这么假设:
小快每秒跑200m,小慢每秒跑100米。两人约定一起绕田径场跑。田径场周长400m。
小快先开始跑,若干秒后小慢也开始跑。以此时为0时刻,画一下他们俩的相对位置:
image.png
2s时刻,小快已经跑完了一圈,小慢也跑到了中点;
image.png
此时,小慢跑到终点还需2s,但是小快跑到终点的时间一定不超过2s。也就是说,最慢在终点之前,小快一定能追上小慢。

也就是说,slow指针一定会在1圈内和fast相遇。

再来看这张图:
image.png

由运动公式 t = s / v t = s/v t=s/v,计算出x的表达式如下:
x + y 1 = x + y + n ( y + z ) 2 x + y = n ( y + z ) x = ( n − 1 ) ( y + z ) + z \begin{align} \frac{x+y}{1} &= \frac{x+y+n(y+z)}{2}\\ x+y &= n(y+z)\\ x &= (n-1)(y+z) + z \end{align} 1x+yx+yx=2x+y+n(y+z)=n(y+z)=(n1)(y+z)+z

可以发现一个非常关键的点:
x的长度等于n-1圈的周长加上z的长度。

这说明什么?如果快慢指针分别同时从O点、链表起点出发,快指针先走长度z到达entry之后,再走n-1圈回到entry,而此时慢指针才刚好走到entry。
也就是说,二者以相同的速度,都走x步,一定会在entry处相遇
也就是说,两个指针分别从O点和起点出发,一定会在环入口处相遇!
所以,n的值不重要无论快指针兜兜转转多少圈,无论慢指针离环的入口有多远,它们一定会相遇
诶,是不是有点像算法第一深情160.相交链表了?哈哈。

捋一捋我们上面说的两个关键点:

  1. 声明快慢指针,快指针速度是慢指针的两倍,二者一定会在交点或null相遇;
  2. 声明两个指针分别从起点和相遇点出发,一定会在入口处相遇。

想通之后,编码就非常简单了。
由于快慢指针会在一开始时相遇,就用了do-while。

if(head==null)  return null;  

ListNode fast = head,slow = head;  

do{  
    slow = slow.next;  
    if(fast!=null){  
        if(fast.next!=null){  
            fast = fast.next.next;  
        }else{  
            return null;  
        }  
    }  
}while (slow!=null && slow != fast);  

//二者在null处相遇,说明没有环  
if(fast==null)  return null;  

//fast不动,slow回到起点,一起前进  
slow = head;  
while (slow != fast){  
    slow = slow.next;  
    fast = fast.next;  
}  
return fast;
  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值