142. 环形链表 II(加计算环的长度)

题目

142. 环形链表 II


给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

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

不允许修改 链表。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 链表中节点的数目范围在范围 [0, 10^4]
  • -10^5 <= Node.val <= 10^5
  • pos 的值为 -1 或者链表中的一个有效索引

进阶: 你是否可以使用 O(1) 空间解决此题?

题目分析

题目给出了一个链表的头节点head,要求找到链表开始入环的第一个节点。如果链表中不存在环,则返回null

题目中还提到了一个整数pos,该整数表示链表尾部连接到链表中的位置的索引(索引从0开始)。但是需要注意的是,pos不作为参数传递,仅仅是为了标识链表的实际情况。

进一步理解题意,可以得到以下几个关键信息:

  1. 如果链表中存在环,那么环的入口节点之前的所有节点都不在环内。
  2. 链表中的节点数量范围在[0, 10^4]内。
  3. pos表示环的入口节点在链表中的索引,取值范围是[-1, 10^4]。

解题思路

这道题的解题思路可以借鉴快慢指针的思想。

我们可以使用两个指针,一个慢指针(slow)每次前进一步,一个快指针(fast)每次前进两步。通过判断两个指针是否相遇,可以确定链表中是否存在环。

在快慢指针相遇之后,将快指针重新指向链表的头节点,从链表的头节点开始和慢指针同步幅移动,直到它们再次相遇。相遇的位置就是环的入口节点。

为什么这样能找到环的入口呢?我们来分析一下:

假设链表的头节点到环的入口节点的距离为a,环的入口节点到快慢指针相遇的节点的距离为b,环的长度为c。

当慢指针走过距离s = a+b时,快指针已经走过了2(a+b)的距离。而快指针走过的距离等于慢指针走过的距离加上多次环的长度,即2(a+b) = a+b+nc,整理得到a + b = nc , 要想找到环的入口也就是找到a的长度,我们需要慢指针在环里转圈转到开始的位置,也就是慢指针走的路程是让s等于a+mc,现在s = a + b = nc,所以再让慢指针走一个a就可以了,但是我们又不知道a具体是多少,是不是再用一个指针开始从头和它一块移动就好了?两者还会在a处相遇。

根据上述分析,我们可以发现,当快慢指针相遇时,如果我们再让一个指针从头节点开始与慢指针同步移动,它们最终会在环的入口节点相遇。因此,该相遇位置即为环的入口节点。

代码实现

基于以上分析,我们可以实现以下代码:

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return None
        
        slow = head
        fast = head
        
        # 判断是否存在环,快慢指针相遇
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        
        # 如果快指针为空,说明链表中不存在环
        if not fast or not fast.next:
            return None
        
        # 将快指针重新指向头节点
        fast = head
        
        # 快慢指针继续移动,直到相遇
        while slow != fast:
            slow = slow.next
            fast = fast.next
        
        return fast

这段代码首先检查特殊情况:链表为空或只有一个节点,则肯定不存在环,直接返回None

然后,我们定义了两个指针slowfast,并且初始化都指向链表的头节点。

接下来,使用快慢指针判断链表是否存在环。快指针每次走两步,慢指针每次走一步,如果它们相遇了,说明链表中存在环,否则不存在。由于这个过程可能会遍历整个链表,所以时间复杂度是O(N),其中N是链表中节点的数量。

如果链表中不存在环,那么快指针最终会指向链表尾部的None,此时直接返回None

如果链表中存在环,那么快慢指针相遇的位置即为环内的某个节点。接下来,我们将快指针重新指向链表的头节点,然后快慢指针同时每次前进一步,直到它们再次相遇。相遇的节点就是环的入口节点。

最后,返回链表的入口节点。

复杂度分析

  • 时间复杂度:O(N),其中N是链表中节点的数量。在最坏情况下,我们需要遍历整个链表才能确定是否存在环,以及找到环的入口节点。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

扩展

计算环的长度
快慢指针

在快慢指针相遇后,可以继续移动慢指针,并且记录移动的步数,直到再次回到相遇点。记录的步数即为环的长度。

def detectCycleLength(head):
    slow = head
    fast = head
    has_cycle = False

    # 判断是否存在环,并找到相遇点
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            has_cycle = True
            break
    
    # 如果不存在环,则返回False
    if not has_cycle:
        return False
    
    # 计算环的长度
    cycle_length = 0
    while True:
        slow = slow.next
        cycle_length += 1
        if slow == fast:
            break

    return cycle_length

在这个方法中,我们使用快慢指针找到相遇点,然后通过移动慢指针并记录步数的方式计算环的长度。如果链表中不存在环,则直接返回False。

这种方法的时间复杂度为O(n),其中n是链表的长度。空间复杂度为O(1),因为我们只使用了两个指针来遍历链表,没有使用额外的数据结构。

哈希表

这种方法使用哈希表来记录每个节点的访问情况。具体步骤如下:

  1. 创建一个空的哈希表。
  2. 从链表的头节点开始,依次遍历链表中的每个节点。
  3. 检查当前节点是否已经在哈希表中存在,如果存在,则表示链表存在环,返回环的长度;否则,将当前节点加入到哈希表中。
  4. 如果遍历完整个链表都没有发现环,则返回False表示链表中不存在环。

以下是使用哈希表计算环的长度的代码示例:

def detectCycleLength(head):
    visited = set()
    node = head
    cycle_length = 0

    while node:
        if node in visited:
            return cycle_length
        visited.add(node)
        node = node.next
        cycle_length += 1

    return False

这种方法的时间复杂度为O(n),其中n是链表的长度。由于使用了哈希表来存储节点,空间复杂度也为O(n),因为最坏情况下需要存储整个链表的所有节点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值