题目
给定一个链表,若其中包含环,则输出环的入口节点。若其中不包含环,则输出null。
给定如上所示的链表:
[1, 2, 3, 4, 5, 6]
2
则输出环的入口节点3.
注意,样例里的2表示编号是2的节点,节点编号从0开始。所以编号是2的节点就是val等于3的节点。输出3。
思路
使用快慢指针法。有两种做法,推荐第一种。
Ps:链表之所以为链,就是因为指针。所以链表的题解本质就是对指针的各种操作:双指针,快慢指针,周期性……
算法1(推荐)
【1】判断是否有环。设置快慢指针,快指针每次走两步,慢指针每次走一步,若快慢指针相遇,则说明该链表包含环。在相遇点处停止快慢指针,并记录相遇点(在该处保留慢指针)。若快指针已经走完整个链表(走到None)仍然没有相遇过,说明该链表没有环。
【2】找出环的入口。设置两个指针,一个指针位于链表头,一个指针位于相遇点,同时开始,每次都走1步,相遇时,相遇点即为环的入口。(将快指针重置在链表头,与相遇点处保留的慢指针一起移动,步长均为1,直到相遇)
证明:
如上图所示,a 是起点,b 是环的入口,c 是两个指针的第一次相遇点,ab 之间的距离是 x,bc 之间的距离是 y。 则当 slow 走到 b 时,由于 fast 比 slow 多走一倍的路,所以 fast 已经从 b 开始在环上走了 x 步,可能多余1圈,距离
b 还差 y 步(这是因为第一次相遇点在 b 之后 y 步,我们让 slow 退 y 步回到 b 点,则 fast 会退 2y
步,也就是距离 b 点还差 y 步);所以 fast 从 b 点走 x+y 步即可回到 b 点,所以 fast 从 c 点开始走,走 x
步即可恰好走到 b 点,同时让 slow 从头开始走,走 x 步也恰好可以走到 b 点。所以第二次相遇点就是 b 点。
另一种思路,可以用公式来说明:a,b,c,x,y 的含义同上,我们用 z 表示从 c 点顺时针走到 b 的距离。则第一次相遇时 fast
所走的距离是 x+(y+z)∗n+y , n 表示圈数,同时 fast 走过的距离是 slow 的两倍,也就是 2(x+y),所以我们有
x+(y+z)∗n+y=2(x+y),所以 x=(n−1)×(y+z)+z,n>=1。n=1的时候,也就是fast比slow多走一圈相遇,此时有x==z,也就是
如果fast和slow以相同速度分别从a和c出发,他们将会在b再次相遇,即为所求。
时间复杂度
slow 总共走了 2x+y 步,fast 总共走了 2x+2y+x 步,所以两个指针总共走了 5x+3y 步。由于当第一次 slow 走到 b 点时,fast 最多追一圈即可追上 slow,所以 y 小于环的长度,所以 x+y 小于等于链表总长度。所以总时间复杂度是 O(n)。
代码
Tips:
while循环结束后直接return,同时包含了break+程序末尾return的功能,推荐使用
否则,这里需要设置tag,以便在程序末尾分情况讨论return
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def entryNodeOfLoop(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if head==None or head.next==None:
return None
f=head
s=head
#tag=False
while f and f.next:
f=f.next.next
s=s.next
if f==s:
#tag=True
f=head
while s!=f:
f=f.next
s=s.next
#break
return f ## 推荐这种返回方式
return None
#return f if tag else None
算法2
【1】判断是否有环。设置快慢指针,快指针每次走两步,慢指针每次走一步,若快慢指针相遇,则说明该链表包含环。停止快慢指针,记录相遇结点。
【2】判断环内有多少个结点。设置一个指针指向相遇点,另一个从相遇点开始走,如果再次回到相遇点,则说明已经走了一圈,可得到环内的结点数count。
【3】找出环的入口。设置两个指针指向链表头,其中一个指针先走count步,然后同时开始走,若两个指针相遇,相遇点即为环的入口。