问题描述:
Given a linked list, return the node where the cycle begins. If there is no cycle, return null
.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?
原问题链接:https://leetcode.com/problems/linked-list-cycle-ii/
问题分析
和前一个问题比起来,这个问题相对于前面一个问题更进一步。在这里不但要看是否存在有环,而且还要在有环的情况下判断那个存在环的入口在哪里。我们这里先讨论存在环的情况,对于一个单链表来说,它存在环的话只可能是类似于如下的形式:
假设从链表的开头到这个环的起始点的距离是a,而环的长度是b。在按照前面采用快慢指针的方式遍历环的时候,它们必然会在环的某个点相遇,假设这个点离环的起始点的距离为x。那么按照前面的假设,快指针肯定是走完了整个链表并加上x的这一段。它走过的距离应该是fast = a + nb + x(其中n表示快指针可能走过的环的圈数,n >= 1),而慢指针走过的距离是a + x,也就是说slow = a + x。同时,按照速度来说,快指针是慢指针的两倍,于是有 fast = 2 x slow。也就是说有 a + nb + x = 2a + 2x。
这样就有a = nb - x。也就是说,从链表的起始点到这个环的入口就是从两个指针相遇的点到环的起点再加上若干个环的距离,这里可能是0个或者多个环的距离。那么,从代码实现的角度来看,它里面不管走多少个环的距离,在环里头相当于还是回到了它原来的地方。而在环里头距离环入口x的位置再走个n -x的距离,它不就正好走到环的入口点了么?
这就是问题的关键所在,从链表的入口走到环的入口和从环里头两个相遇的节点的位置不断往前走的时候,它们会在环的入口位置相遇。所以,我们在实现的时候首先找到两个指针相遇的地方。然后再从链表的开头走一个指针过来,和环里面那个slow指针一起走。一旦它们相遇,那么那个点就是环的入口了。
详细代码实现如下:
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode detectCycle(ListNode head) { if(head == null || head.next == null) return null; ListNode fast = head, slow = head; while(fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; if(fast == slow) break; } if(fast == null || fast.next == null) return null; slow = head; while(fast != slow) { fast = fast.next; slow = slow.next; } return slow; } }