Loading

【HOT 100】142. 环形链表 II

142. 环形链表 II

题目描述

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

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

方法一:快慢指针

判断一个链表是否有环,可以通过快慢指针实现,快指针每次走两步,慢指针每次走一步,若相遇,则说明有环。
算法思路:

  1. 定义三个指针,一个环头指针,用于对链表实现一次遍历,获取环头节点;另外两个快慢指针,判断是否存在环。
  2. 环头指针从链表头开始遍历,每移动一个节点,快慢指针从当前节点出发,判断是否存在环,且记录当前节点是否在环中。
    时间复杂度:O(n^2),空间复杂度O(1)
ListNode *detectCycle2(ListNode *head)
{
    ListNode *fast = head;       // 快指针,每次走两步
    ListNode *slow = head;       // 慢指针,每次走一步
    ListNode *cycle_head = head; // 记录头节点指针,每次快慢指针从头节点开始走
    bool pos = false;            // 标识:当前头节点是否在环中
    bool has_cycle = false;      // 标识:是否存在环

    while (!pos)
    {
        // 判断是否存在环
        while (slow->next != nullptr && fast->next != nullptr && fast->next->next != nullptr)
        {
            fast = fast->next->next;
            slow = slow->next;

            if (fast == cycle_head || slow == cycle_head) // 判断当前头节点是否在环中
                pos = true;

            if (fast == slow) // 判断是否存在环
            {
                has_cycle = true;
                break;
            }
        }

        if ((has_cycle && pos) || cycle_head->next == nullptr) // 找到存在环的头节点,或者遍历结束
            break;
        else
        {
            cycle_head = cycle_head->next;
            fast = cycle_head;
            slow = cycle_head;
        }
    }
    if (pos)
        return cycle_head;
    return nullptr;
}

方法二:快慢指针优化

上一个方法中,是对当前链表遍历时,每个节点都要执行:判断是否是环头节点,并且是否存在环。
实际上,不管从那个节点出发,都对环的存在判断没影响,只需要判断一次就好。所以,关键在于定位环头节点。
官方的思路:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode-solution/

a:链表中环外部分的长度
b: 满指针进入环后,走了 b 的距离与快指针相遇

因此,根据快指针走的距离,有下列等式

a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)

而 b+c 等于环的一圈,不影响 a 的位置,因此可以得出a = c 的结论。
也就是说,快慢指针相遇后,慢指针继续走到环头节点的距离,与环外距离的长度一致,那样接下来算法就很好写了。

ListNode *detectCycle(ListNode *head)
{
    if (!head)
        return nullptr;

    ListNode *fast = head;
    ListNode *slow = head;
    ListNode *cycle_head = head;
  
    // 判断是否存在环
    while (slow->next != nullptr && fast->next != nullptr && fast->next->next != nullptr)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)  // 存在环,跳出
            break;
    }
    
    // 如果存在环
    if (fast->next && slow->next && fast->next->next)
    {
        while (cycle_head != slow)
        {
            slow = slow->next;
            cycle_head = cycle_head->next;
        }
        return cycle_head;
    }

    return nullptr;
}

方法三:hash表

如果不追求O(1)的空间复杂度,可以将遍历过的节点记录在 hash 表中,如果表中已经存在当前结点,说明存在环,且当前结点是环头结点。

posted @ 2021-09-03 14:44  锦瑟,无端  阅读(36)  评论(0编辑  收藏  举报