前言
昨天跟着Carl哥刷完链表的最后一章啦,今天早上睡醒写一遍笔记看看自己还能记住多少。
每天除了上课也只能抽空跟Carl哥学一点学一点的,没想到能坚持到现在,数组和链表自己也算有基础,所以学起来并不是很费解,后面的树、图只有有一点概念而已,并没有上手写过,希望能慢慢掌握,然后像数组和链表一样自己手撕出来!
思路
这道题无非就两个难点:1、如何判断有环? 2、如何判断环的入口?
判断链表有无环,也是运用和数组一样的双指针法,所以就突然意识到双指针玩的好,真的能解决很多问题。
设立一个快指针 fast 每次走两个结点,慢指针 slow 每次走一个结点。如果链表有环的话,那么快指针 fast 一定会比慢指针 slow 先一步进环内,然后在环里转圈圈,等慢指针 slow 进入环内,当快慢指针相遇时,就证明了这个链表是有环的。
那么就有一个问题:快慢指针不会在环内错开吗?
我们清楚快指针 fast 每次走两个结点, 而慢指针 slow 每次走一个结点,当这两个指针进入环内以后,就相当于慢指针 slow 不动,快指针每次以一个结点一个结点前进的速度去追慢指针slow,所以肯定不会错开的,这样我们也能得出两个结论:1、当慢指针进入环时,快指针一定在环内转了一圈或一圈以上。 2、快指针一定能在一圈内追上慢指针
这是两个很重要的结论,后面也会再证明一遍。
如果链表有环,如何判断环的入口?
我们可以假设从头结点到环的入口距离为x,环形入口到相遇结点的长度为y,相遇节点到环形入口的长度为z。
画图相当于这样,画的很丑不过无所谓....
我们可以得出三个公式:
- 快指针的移动距离:x + y + n(y + z) (n 是快指针在环内所转圈数)
- 慢指针的移动距离:x + y
- 快指针走过的结点数 = 2 * 慢指针走过的结点数:(x + y) * 2 = x + y + n (y + z)
化简以后就可以得出: x = n (y + z) – y (x就是我们要求的头结点到入口的距离)
这里有个 -y 我们把 x 化为正数比较好,就是在 n 中提取出一圈的 (y + z)与 -y 抵消掉,化简后相当于: x = (n - 1)(y + z) + z
这里证明一下 n 肯定 >= 1 的,因为快指针fast至少要多走一圈才能相遇慢指针slow,慢指针也一定是进入环形后第一圈内就被快指针追上的,如下图假设把环拉直:
所以当 n = 1时,我们可以得出一个结论: x = z
即:相遇位置到环入口的距离 = 头结点到环入口的距离
跟着Carl哥得出这个,我人都麻了太顶了这个证明,有了这个证明,就可以定义两个指针,一个从相遇位置往后走,一个从头结点往后走,他们两个相遇的位置,就一定是环的入口。
有了以上思路,我们很容易就能写出题解了。
C++代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode * fast = head, * slow = head;
while(fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if(fast == slow){ // 有环出现
ListNode * index = fast, * index2 = head;
while(index != index2){ // 找环入口
index = index -> next;
index2 = index2 -> next;
}
return index;
}
}
return NULL; // 链表无环返回空
}
};