刷到了这样一道题目,非常有意思,让我的大脑感受到了前所未有的重创,现在把这道题目的题解分享一下。
1.题目
图片看不清的话这里有原题文字:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
2.题目分析
这道题是根据《环形链表I》改编过来的,环形链表I只需要判断这个链表是不是环形就可以了,所以比较简单,但是这道题目需要使用哈希表才能简单解决,在官方题解里,哈希表是更典型的题解,但是,对于我来说,我目前还没有学到哈希表,所以只能使用快慢指针+纯粹的数学思想解决。
首先,最首要的就是判断是否成环,然后才是找到入环节点,这就是这题的核心思路。
判断是否成环,这个比较简单,也比较容易,使用快慢指针就可以了,即快指针一次走两步,慢指针一次走一步,如果走的过程中快指针和满指针相遇了,就表示这个成环,如果走的过程中快指针或慢指针或者快指针的下一个节点任意一个为空,都说明这个链表没有成环。
· 那么就可以写出这段的代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode * cur1 = head;
struct ListNode *cur2 = head;
while(cur1 && cur2 &&cur1->next && cur2 ->next)
{
cur1 = cur1 ->next;
cur2 = cur2 ->next->next;
if(cur1 == cur2)
{
//在这里找到相遇节点
//return cur1;
}
}
return NULL;
}
上面的也就是《环形链表I》的最优解法思路。
第二步就是找到入口点,指针都已经进来了,要怎么找呢?我们不妨画画图分析一下。
这里有一个非常重要的点,就是慢指针进入圈后走不到一圈就一定会和快指针相遇,这样说的话,就可以假设极端情况:
最好情况就是:满指针刚走到入环节点就正好与快指针相遇,也就是说:慢指针进入环走的距离s == 0,这就是最好情况;
然后就是最坏情况了:慢指针进入环之后刚好快指针就在慢指针前面距离为1的位置,那么就会有快指针想要追上慢指针,就要补上这n-1的距离,也就是从进入环后慢指针走s距离,快指针走2s距离之后,二者相遇,就有2s -s = n-1,所以得到s = n - 1。
也就是按最坏情况来说的话,慢指针最多还要走n - 1的距离就能与快指针相遇,所以慢指针进入圈内之后,想要和快指针相遇最多走的距离不会超过一圈。
所以,我们再分析一下快指针和慢指针从起点到相遇走的总路程,慢指针走的总路程就是m+s ,快指针走的路程就是m+s +k*n(这里的k是一个正整数,表示快指针比满指针多走了k圈才和慢指针相遇),快指针总是比慢指针快,并且快指针速度是慢指针的两倍,走的路程也就是慢指针的两倍,那么就有 2(m+s) = m + s + k*n,所以得到 m = k*n - s
我们想要得到入环节点,这个表达式m = k * n - s就必不可少,那么我们要怎么得到这个入环节点呢?已知:满指针已经走到了m+s的位置,那么这个时候我们再引入一个指针ptr从初始节点和慢指针同时以与慢指针相同的速度开始走,ptr走到入环节点的时候ptr走的距离是m ,同时慢指针就又走了m距离,那么m = k *n - s,之前慢指针走的距离是m + s ,加上这里的m ,也就是说慢指针从起点到这个时候走了m + s + m = m + k * n 的距离,正好就是m距离加上k * n (整数圈),所以当ptr走到入环节点的时候,会和慢指针相遇
依照上面的思路我们就可以把代码补全:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode * cur1 = head;
struct ListNode *cur2 = head;
while(cur1 && cur2 &&cur1->next && cur2 ->next)
{
cur1 = cur1 ->next;//慢指针
cur2 = cur2 ->next->next;//快指针
if(cur1 == cur2)
{
struct ListNode * ptr = head;
while(ptr!=cur1)
{
ptr = ptr->next;
cur1 = cur1->next;
}
return ptr;
}
}
return NULL;
}
然后提交代码,就顺利通过了: