1.1 题目描述
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
数据范围:链表长度 0≤𝑛≤10000,链表中任意节点的值满足 ∣𝑣𝑎𝑙∣<=100000
要求:空间复杂度 𝑂(1),时间复杂度 𝑂(𝑛)
输入分为两部分,第一部分为链表,第二部分代表是否有环,然后将组成的head头结点传入到函数里面。-1代表无环,其它的数字代表有环,这些参数解释仅仅是为了方便读者自测调试。实际在编程时读入的是链表的头节点。
例如输入{3,2,0,-4},1时,对应的链表结构如下图所示:
可以看出环的入口结点为从头结点开始的第1个结点(注:头结点为第0个结点),所以输出true。
示例1
输入:{3,2,0,-4},1
返回值:true
说明:第一部分{3,2,0,-4}代表一个链表,第二部分的1表示,-4到位置1(注:头结点为位置0),即-4->2存在一个链接,组成传入的head为一个带环的链表,返回true
示例2
输入:{1},-1
返回值:false
说明:第一部分{1}代表一个链表,-1代表无环,组成传入head为一个无环的单链表,返回false
示例3
输入:{-1,-7,7,-4,19,6,-9,-5,-2,-5},6
返回值:true
1.2 算法思想
设置fast与slow两个指针在链表的起始处,让slow 指针每次向后移动一个位置,fast 指针每次向后移动两个位置。
如果链表中存在环,两个指针就会在环内不断循环,且因为有速度差异, fast 指针与 slow 指针一定会在在环中相遇。
时间复杂度O(N):其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度
空间复杂度O(1):额外使用的指针占用常数空间
1.3 代码
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# @param head ListNode类
# @return bool布尔型
#
class Solution:
def hasCycle(self , head: ListNode) -> bool:
if not head:#链表为空
return False
fast=head#快指针
slow=head#慢指针
while fast and fast.next:
slow=slow.next#慢指针移动一步
fast=fast.next.next#快指针移动两步
if fast==slow:#两指针相遇,一定有环
return True
return False #此时快指针到了链表末尾(fast.next==None),有环的链表不存在末尾
2.1 进阶版题目——判断环的入口
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则返回null。
数据范围: 𝑛≤10000,1<=结点值<=10000
要求:空间复杂度 𝑂(1),时间复杂度 𝑂(𝑛)
例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:
可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。
输入描述:
输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台会根据第二段是否为空将这两段组装成一个无环或者有环单链表。
返回值描述:
返回链表的环的入口结点即可,后台程序会打印这个结点对应的结点值;若没有,则返回对应编程语言的空结点即可。
示例1
输入:{1,2},{3,4,5}
返回值:3
说明:返回环形链表入口结点,我们后台程序会打印该环形链表入口结点对应的结点值,即3
示例2
输入:{1},{}
返回值:"null"
说明:没有环,返回对应编程语言的空结点,后台程序会打印"null"
示例3
输入:{},{2}
返回值:2
说明:环的部分只有一个结点,所以返回该环形链表入口结点,后台程序打印该结点对应的结点值,即2
2.2 算法思想
根据上题思想,设置fast与slow两个指针在链表的起始处,让slow指针每次向后移动一个位置,fast 指针每次向后移动两个位置。如果链表中存在环,两个指针就会在环内不断循环,且因为有速度差异,fast指针与slow指针一定会在在环中相遇。
那么相遇点和环入口节点有什么关系呢?已知相遇点怎么找到环入口节点呢?
不妨设快指针在环中走了n圈,慢指针在环中走了m圈,它们才相遇,而进入环之前的距离为x,环入口到相遇点的距离为y,相遇点到环入口的距离为z,详见下图:
快指针一共走了x+n(y+z)+y,慢指针一共走了x+m(y+z)+y,这个时候快指针走的倍数是慢指针的两倍,联立得x+n(y+z)+y=2(x+m(y+z)+y),化简得x+y=(n−2m)(y+z)。又因为环的大小是y+z,说明从起始点经过环入口到达相遇点经过的距离(x+y)等于整数(n−2m)倍环(y+z)的大小,这里不妨假设(n−2m)为自然数。
此时我们再使用两个指针,一个指针从起始点遍历相到遇点,它走了x+y,另一个指针从相遇点开始在环中遍历(n−2m)圈,二者最后都会经过y这段路(图中蓝色圆弧)再次在相遇点相遇 (因为x+y=(n−2m)(y+z) ),即最后这y个节点它们是重叠遍历的,说明它们从环的入口处就相遇了。
所以第一次相遇后,我们再使用两个指针,一个从链表起始点开始,一个从相遇点开始,每次它们都向后走一步,直到再次相遇,那么这个相遇点就是环的入口。
2.3 代码
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
fast=pHead
slow=pHead
slow2=pHead
while fast and fast.next:
fast=fast.next.next
slow=slow.next
if fast==slow:
while slow!=slow2:
slow=slow.next #slow指针直接在相遇点处开始在环内遍历
slow2=slow2.next#另用一个新指针slow2从起始点开始遍历
return slow #退出循环时,两指针指向环的入口相遇
return None