环形链表

标签: 哈希表 链表 双指针

难度: Easy

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos-1 或者链表中的一个 有效索引

进阶:你能用 O(1)(即,常量)内存解决此问题吗?

Submission

运行时间: 60 ms

内存: 18.5 MB

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if head is None:
            return False
        
        slow = fast = head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                return True
        return False

Explain

本题解使用了快慢指针的思路。初始化两个指针slow和fast,它们都指向链表的头节点。接下来,slow指针每次移动一步,fast指针每次移动两步。如果链表中存在环,那么fast和slow最终会在环中的某个节点处相遇。while循环的条件是fast和fast.next都不为None,这样可以防止fast指针越界。如果fast和slow相遇,说明链表中有环,返回True;如果fast或fast.next为None,说明到达了链表的尾部,链表中不存在环,返回False。

时间复杂度: O(n)

空间复杂度: O(1)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        # 如果链表为空,直接返回False
        if head is None:
            return False
        
        # 初始化快慢指针,都指向链表的头节点
        slow = fast = head
        # 当fast和fast.next都不为None时,继续循环
        while fast and fast.next:
            # fast指针每次移动两步
            fast = fast.next.next
            # slow指针每次移动一步
            slow = slow.next
            # 如果fast和slow相遇,说明链表中有环,返回True
            if fast == slow:
                return True
        # 如果fast或fast.next为None,说明链表中不存在环,返回False
        return False

Explore

快慢指针方法是一种空间复杂度较低的解决方案,空间复杂度为O(1),因为它只需要两个指针变量。相比之下,使用哈希表来检测环需要存储已访问过的节点,这将增加额外的空间复杂度,通常为O(n),其中n是链表中的节点数。此外,快慢指针方法也可以在不修改链表结构的情况下检测环,避免了对原数据结构的干扰。

在快慢指针策略中,选择fast指针每次移动两步是为了保证算法的效率和正确性。如果fast指针每次移动超过两步,虽然可能提高遍历速度,但也增加了在链表中存在环的情况下错过慢指针的风险。当fast指针移动两步,而slow指针移动一步时,可以确保如果存在环,fast指针一定会在某个时刻与slow指针相遇,从而验证环的存在。此外,这种设置也有利于保证算法的简洁性和理解性。

如果快指针追上慢指针,这确实意味着链表中一定存在环。在一个无环的链表中,快指针将会先达到链表的末尾,而不会追上慢指针。只有在链表中存在环时,快指针在绕环运行时才有可能从后方追上慢指针,因为快指针每次循环都在减少与慢指针之间的距离。

在没有环的链表中,`fast.next`或`fast`变成`None`的情况发生在快指针到达链表的末尾。这种情况确实表明链表一定没有环。如果存在环,快慢指针将会无限循环于环内,而不会遇到`None`。因此,当检测到`fast`或`fast.next`为`None`时,可以确定链表中不存在环。