问题描述
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
解决方案
快慢指针法
想象一下,有两个速度不同的跑步者。如果他们在直路上行驶,快跑者将首先到达目的地。但是,如果它们在圆形跑道上跑步,那么快跑者如果继续跑步就会追上慢跑者。
这正是我们在链表中使用两个速度不同的指针时会遇到的情况:
如果没有环,快指针将停在链表的末尾。
如果有环,快指针最终将与慢指针相遇。
所以剩下的问题是:
这两个指针的适当速度应该是多少?
一个安全的选择是每次移动慢指针一步
,而移动快指针两步
。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。
时间复杂度:O(n)
空间复杂度:O(1)
show me the code
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
quick_pointer = slow_pointer = head # 创建两个指针,分别记录一个快的遍历指针,和一个慢的便利指针
while quick_pointer and quick_pointer.next: # 当快指针为None,或快指针的下个一对象为None,说明不是环形链表
slow_pointer = slow_pointer.next # 慢指针前进一步
quick_pointer = quick_pointer.next.next # 快指针前进两步
if slow_pointer == quick_pointer: # 假如快指针等于慢指针,说明为环形链表
return True
return False
hash表存贮法
顺序遍历链表所有节点,若出现重复访问则说明有环,否则说明无环。这里注意不能用list保存访问过的节点,查找太慢了;用dict保存还要考虑到键不能是对象,所以这里采取以对象的id作为键的做法。
时间复杂度:O(n)
空间复杂度:O(n)
show me the code
class Solution2(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
my_dict = {} # 创建一个字典来保存已经遍历过节点的内存地址
while head and head.next: # 当快指针为None,或快指针的下个一对象为None,说明不是环形链表
if id(head) in my_dict: # 假如当前节点在字典中则说明是环形链表
return True
else:
my_dict[id(head)] = True # 将当前节点加入到字典中
return False
逆转链表检测法
倘若一个链表存在环,那么将这个链表反转,反转后的链表和原链表具有相同的head。证明起来比较麻烦,可以在纸上画一画来验证。
时间复杂度:O(n)
空间复杂度:O(n)
show me the code
class Solution3(object):
def reverse_list(self, head):
before = after = None
while head:
after = head.next # 保存当前节点的下一个节点
head.next = before # 将当前节点的下一题个节点替换为当前节点的上一个节点
before = head # 将上一个节点,往前移动,变为当前节点
head = after # 当前节点向前移动
return before # 返回反转完成后的头结点
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
if head and head.next and head == self.reverse_list(head): # 加入反转后的头结点与原先的头结点相同,则说明有环
return True
return False