Cycle Detect
環檢測算法常用檢測鏈表是否有環,如果有環,給出環的長度和環的入口點。
相關題目: 287. Find the Duplicate Number,141. Linked List Cycle,142. Linked List Cycle II
參考:Leetcode solution,簡書-面試算法:鏈表成環
分析
當兔子和烏龜在環形跑道上跑步,在某一時刻,兔子會追上烏龜。
算法
算法可以分成兩個步驟,第一個步驟是確定鏈表是否有環,第二個步驟是確定環的入口點在那里。
步驟1
首先,我們初始化兩個指針,一個快速的 hare 指針,一個慢的Tortoise 指針。讓hare 一次走兩個節點,Tortoise 一個節點。最終,Tortoise 和hare 總會在相同的節點相遇,這樣就可以證明是否有環。
這里,循環中的節點已經被標記為0到 C-1,在這里 C是環的長度。非環節點已經被標記 -F到-1,在這里F是環外的節點數量。在F次迭代后,tortoise指向節點0,并且hare指向某個節點 h。??這是因為hare遍歷2F節點的過程遍歷了F次,F點仍在循環中。后面迭代C - h次,tortoise顯然指向節點C - h,但hare也指向相同的節點。要明白為什么,請記住hare遍歷2(C - h )從其起始位置開始H:
?h+2(C?h)?=2C?h?≡C?h(modC)
因此,鑒于列表是有環的,hare和tortoise最終都將指向同一個節點,所以這個節點可以作為后續的第一次相遇的點。
步驟2
考慮到步驟1發現一個交叉點,步驟2繼續尋找環入口的節點。為此,我們初始化兩個指針:ptr1指向列表頭部的指針,指向ptr2交叉點的指針 。然后,我們把他們每個每次前進1個節點,直到他們相遇; 他們相遇的節點是環的入口,我們就可以得出結果。
我們可以利用hare移動步數是tortoise的兩倍,以及tortoisehare和tortoise節點在h相遇,hare已經遍歷了兩倍的節點。使用這個事實,我們推導出以下內容:
2?distance(tortoise)?=distance(hare)
?2(F+a)?=F+a+b+a
?2F+2a?=F+2a+b
?F?=b
因為 F = b,指針從節點開始h和0在相遇之前, 將遍歷相同數量的節點。
代碼
public class Solution {
private ListNode getIntersect(ListNode head) {
ListNode tortoise = head;
ListNode hare = head;
// A fast pointer will either loop around a cycle and meet the slow
// pointer or reach the `null` at the end of a non-cyclic list.
while (hare != null && hare.next != null) {
tortoise = tortoise.next;
hare = hare.next.next;
if (tortoise == hare) {
return tortoise;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// If there is a cycle, the fast/slow pointers will intersect at some
// node. Otherwise, there is no cycle, so we cannot find an entrance to
// a cycle.
ListNode intersect = getIntersect(head);
if (intersect == null) {
return null;
}
// To find the entrance to the cycle, we have two pointers traverse at
// the same speed -- one from the front of the list, and the other from
// the point of intersection.
ListNode ptr1 = head;
ListNode ptr2 = intersect;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1;
}
}