142. 环形链表 II

118 阅读2分钟

[142. 环形链表 II]

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。

题目描述

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

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

不允许修改 链表

示例

示例1 :

输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]

示例 2:

输入: head = [1], n = 1
输出: []

示例 3:

输入: head = [1,2], n = 1
输出: [1]

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos 为 -1 或者链表中的一个 有效索引 。   进阶: 你能用 O(1)(即,常量)内存解决此问题吗?

思路

在之前的文章里面,我们已经能够判断链表里面是否有环了,那么怎么找环的入口呢,即跑道的入口?之前的说过的哈希表,第一个相等的就是环的入口,那么双指针得怎么修改呢?按照之前的写法,我们可以得到在环内相遇的节点。下来又到了数学上的追及问题了,我们还是假设从一段直道跑到环形跑道的跑道上,我们设直道为x,从环道入口到相遇点为y,相遇点到环形入口为z。在环里相遇时,slow指针走了 x+y ,fast走了 x+y+n(y+z) ,n为fast跑的圈数。且至少为一,并且x+y是已知的。又因为fast的速度为slow的2倍,所以有2(x+y)=x+y+n(y+z),我们求环道入口其实就是离起点x的地方,也是离相遇点z的地方,所以,我们从起点走x,或者走x+n(y+z) (n>=1),都会到相遇的地点。所以于我们整理得 x = n (y + z) - y,不妨假设 n=1 ,那么可以到 x=z 的结论,当 n>1 时,将x = n (y + z) - y 代入 x+n(y+z) 可知走(2n-1)(y+z)+z 可到达环道入口。总上,从起点走z,走(2n-1)(y+z)+z,都可以到达入口。

代码实现

求离起点距离为z的节点,且x+y也已知,而且从x+y在走z,即走x+y+z 也会是到达相遇点。可以以此作为编码的基础。走(2n-1)(y+z)+z,在链表上的实际距离恰好也是走z的距离。怎么走可以参考

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast!= nullptr &&fast->next!=nullptr) {
            slow = slow->next;
            fast = fast->next->next;      //找x+y
            if(slow==fast){
              ListNode* FormMeet=fast;   //记录x+y
                ListNode* FromHead=head;    //开始走z
                while(FormMeet!=FromHead)     
                {
                    FormMeet=FormMeet->next;
                    FromHead=FromHead->next;
                } 
                return FormMeet;
            }
        }
        return nullptr;
    }
};

总结

在链表上使用双指针,最最重要的一点是要挖掘出双指针的指向需要满足什么样的条件,才能符合我的要求。这道题针对了双指针的速度进行了讨论。