本文解決三個問題:
1.單鏈表是否有環?
2.有則輸出環的長度?
3.找到環的入口節點?
分析:
定義兩個指針fast 和slow,fast每次向後移動兩個節點,slow每次想後移動一個節點。
1.如果沒有環,則fast首先到達鏈表結尾;
2.鏈表有環的情況下:fast與slow兩次相遇,slow中間走過的節點處即爲環的長度;
3.找環的入口節點稍微複雜點,有如下的推導過程:
相遇的時候,slow共移動了s步,fast共移動了2s步。
定義a如下: 鏈表頭移動a步到達入口點。
定義x如下: 入口點移動x步到達相遇點。
定義r如下: 環的長度。
定義L如下: 鏈表總長度爲L。
其中L = a + r
那麼slow和fast相遇了,fast必然比slow多走了n個圈,也就是 n*r 步,那麼
s = a + x
2s = s + n*r , 可得 s = n*r
將s=a+x,帶入s =n*r,可得 a+x = n*r, 也就是 a+x = (n-1)*r + r
從表頭移動到入口點,再從入口點移動到相遇點,也就是移動了整個鏈表的距離,即是 L = a + r , 所以r = L - a
所以 a+x = (n-1)*r + L - a , 於是 a = (n-1)*r + L - a - x
得到:從表頭到入口點的距離,等於從相遇點到入口點的距離。
所以,從表頭設立一個指針,從相遇點設立一個指針,兩個同時移動,必然能夠在入口點相遇,這樣,就求出了相遇點。
上面三個問題的java解決代碼:
方法一:一次性求解
public class ExistCircle {
static int id = 1;
public Node existCircle(Node head){
Node fast = head;
Node slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
//輸出環長
while(fast == slow) {
int len = 1;
fast = fast.next.next;
slow = slow.next;
while (fast != slow) {
len++;
fast = fast.next.next;
slow = slow.next;
}
System.out.println("The length of circle is:" + len);
//輸出環入口節點
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
id++;
}
return slow;
}
}
return null;
}
public static void main(String[] args) {
Node head=new Node(3);
Node node1=new Node(6);
Node node2=new Node(8);
Node node3=new Node(5);
Node node4=new Node(2);
Node node5=new Node(7);
head.next=node1;
node1.next=node2;
node2.next=node3;
node3.next=node4;
node4.next=node5;
node5.next=node3;
Node port = new ExistCircle().existCircle(head);
System.out.println("環入口爲第" + id + "個節點:" + port.data);
}
}
方法二:分開求解:
package listnode;
/**
* @author Gavenyeah
* @date Time: 2016年5月18日上午11:36:40
* @des:
*/
public class ExitCircle {
static int id = 1;
public static void main(String[] args) {
//測試
Node head=new Node(3);
Node node1=new Node(6);
Node node2=new Node(8);
Node node3=new Node(5);
Node node4=new Node(2);
Node node5=new Node(7);
head.next=node1;
node1.next=node2;
node2.next=node3;
node3.next=node4;
node4.next=node5;
node5.next=node3;
new ExitCircle().exitCircle(head);
Node port = new ExitCircle().findLoopPort(head);
System.out.println("環入口爲第" + id + "個節點:" + port.data);
}
//環入口節點
//環的入口節點到快慢指針相遇的距離 與 鏈表頭節點到環入口節點的距離相等
public Node findLoopPort(Node head){
Node slow = head, fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
fast = head;
while(head != slow){
id++;
head = head.next;
slow = slow.next;
}
return slow;
}
}
System.out.print("NoLoop !");
return null;
}
public boolean exitCircle(Node head){
Node fast = head;
Node slow = head;
while(fast != null && fast.next != null){//判斷是否由環,注意fast.next = null的情況
fast = fast.next.next;
slow = slow.next;
if(fast == slow){//存在環
int count = 0;
while(true){
count ++;
fast = fast.next.next;
slow = slow.next;
if(fast == slow){//快慢指針在第二次相遇,這個點肯定是第一次相遇的點
System.out.println("環的長度:" + count);
return true;
}
}
}
}
System.out.println("false!");
return false;
}
}