力扣 142 环形链表Ⅱ
给定一个链表的头节点 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, 104]内 -105 <= Node.val <= 105pos的值为-1或者链表中的一个有效索引
这道题跟着逻辑走思路很清晰,但实际上需要严谨证明和反复琢磨才能真正理解并独立解题
主要解决两点:
- 判断链表是否成环
- 如果有环,那么环的入口在哪里
一. 是否成环?
这里可以采用双指针,分别定义快慢指针,均从头节点出发,其中 fast 每次移动两个节点,而 slow 每次移动一个节点,如果 fast 和 slow 在移动途中相遇,说明该链表成环。
为什么快慢指针相遇就说明成环?
因为是快指针先入环,慢指针后入环,如果 fast 指针和 slow 指针相遇的话,一定是在环中相遇,在环内相遇就说明肯定成环了。
为什么有环一定会相遇?
本质上是因为两指针存在速度差,在环内绕环移动时,快指针一定会追上慢指针。这是因为 fast 走两步,slow 走一步,对 slow 来说 fast 是一点一点接近它的,所以两者一定会在环内某一节点处相遇。
画一个环,让 fast 在任意一个节点开始追赶 slow,会发现展开后都是下图这种情况:
此时,按照定义 fast往前走一次两步,slow走一次一步,两者就会在 3 处相遇。
二. 入口在哪里?
假设从头结点到入口节点的节点数为 x,入口节点到两指针相遇节点的节点数为 y,从相遇节点再到入口节点的节点数为 z , 如图所示:
slow 指针走过的节点数为 x + y ,fast 指针走过的节点数为 x + y + n * (y + z)
其中 n 表示快指针追慢指针的过程中绕过的圈数。
由于快指针一次走两个节点,慢指针一次走一个,故快指针走过的节点数是慢指针的 2 倍。
也就是 2 * (x + y) = x + y + n * (y + z) ,两边消一个 x + y 化简得 x + y = n * (y + z) 。
要找到是环形入口节点,也就是 x,所以把 x 提出来放左边得 x = n * (y + z) - y 。右边提一个 y + z 出来化简得 x = (y + z) * (n - 1) + z 。
化简到这一步,不妨设环绕圈数 n 为 1 ,即可得 x = z 。这也就是说,从头结点和相遇节点同时出发一个指针,两个指针每次只走一个节点, 那么当这两个指针相遇时所在处就是环形入口的节点。
那么** n 如果大于 1** 是什么情况呢,其实这种情况和 n 为 1 的时候是一样的,一样可以通过这个方法找到入口节点,只不过 index1 指针在环里多转了 (n-1) 圈,然后再遇到 index2 ,相遇点依然是环形的入口节点。
至此,我们完成了一开始提出的两个问题!
✅下面是完整力扣代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2;
}
}
return nullptr;
}
};