問題描述
怎么能夠更高效地判斷一個鏈表是否有環呀?
首先創建兩個指針p1和p2(在Java里就是兩個對象引用),讓它們同時指向這個鏈表的頭節點。
然后開始一個大循環,在循環體中,讓指針p1每次向后移動1個節點,讓指針p2每次向后移動2個節點,然后比較兩個指針指向的節點是否相同。如果相同,則可以判斷出鏈表有環,如果不同,則繼續下一次循環
示意圖


學過小學奧數的讀者,一定聽說過數學上的追及問題。
此方法就類似於一個追及問題。在一個環形跑道上,兩個運動員從同一地點起跑,一個運動員速度快,另一個運動員速度慢。當兩人跑了一段時間后,速度快的運動員必然會再次追上並超過速度慢的運動員,原因很簡單,因為跑道是環形的。
假設鏈表的節點數量為n,則該算法的時間復雜度為O(n)。
除兩個指針外,沒有使用任何額外的存儲空間,所以空間復雜度是O(1)
示例代碼
public class LinkedListCycle {
/**
* 判斷是否有環
* @param head 鏈表頭節點
*/
public static boolean isCycle(Node head) {
Node p1 = head;
Node p2 = head;
while (p2!=null && p2.next!=null){
p1 = p1.next;
p2 = p2.next.next;
if(p1 == p2){
return true;
}
}
return false;
}
/**
* 鏈表節點
*/
private static class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
public static void main(String[] args) throws Exception {
Node node1 = new Node(5);
Node node2 = new Node(3);
Node node3 = new Node(7);
Node node4 = new Node(2);
Node node5 = new Node(6);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node2;
System.out.println(isCycle(node1));
}
}
擴展
如果鏈表有環,如何求出環的長?
當兩個指針首次相遇,證明鏈表有環的時候,讓兩個指針從相遇點繼續循環前進,並統計前進的循環次數,直到兩個指針第2次相遇。此時,統計出來的前進次數就是環長。
因為指針p1每次走1步,指針p2每次走2步,兩者的速度差是1步。當兩個指針再次相遇時,p2比p1多走了整整1圈因此,環長 = 每一次速度差 × 前進次數 = 前進次
