-
判斷鏈表有沒有環
有環鏈表
一般我們采取快慢指針來判斷鏈表是否有環。思路主要是:定義兩個指針。
fast
和slow
;
fast
和slow
都從head
開始往后走。顧名思義,fast
走得快一點,每次走兩步;slow
走得慢一點,每次走一步;
沒有環的情況下,fast
肯定率先走到尾結點;
有環的情況下,fast
先入環,slow
后入環。因為fast
比slow
每次都多走一步,所以最終在某個地方會相遇(就像跑800m的時候,A跑得特別快,B跑得特別慢;A最后在某個地方又追上了B,超了B一圈)。看懂了上面的分析,應該就能寫出相應的代碼。
public class 判斷鏈表是否有環 { public boolean isLoop(ListNode head){ ListNode slow = head; ListNode fast = head; while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ return true; } } return false; } }
-
有環的話找到入口結點
入口結點.png我們要求的是D(入口結點),現在已知的是I(相遇點,按照第一題的快慢指針可以求得)。這道題目有點像數學題,我們來推導一下:
slow
走x步的時候,fast
走了2x步,其中在環內走了x步;
然后我們要知道,第一次相遇的時候slow
還未走完一圈(最多走完一圈),同樣我們想象兩個人跑800m,A速度是2m/s,B是1m/s。A跑完2圈,B跑完1圈的時候兩個人相遇。這是最極端的情況,即兩個人是從同一個起點開始跑的。而在本題內,一般情況下,fast
都會比slow
先多走幾步,這樣fast
追上slow
所用的時間又會少一點;
那么相遇的時候,slow
在環內走了s步,fast
在環內走了x+2s步;
假設fast已經走了n圈。那么有下面的等式:
s=x+2s-nc,即s=nl-x=(n-1)c+c-x;
我們可以把(n-1)c+c-x理解為,fast
先走完(n-1)圈,再走了c-x步。
由此我們可以知道,在相遇點的時候,我們再走x步就能又回到入口結點。那么I到D的距離就是x,和A到D的距離是一樣的然后是代碼:
public class 環的入口結點 { public ListNode EntryNodeOfLoop(ListNode pHead) { ListNode slow = pHead; ListNode fast = pHead; while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ // 相遇就跳出循環 break; } } if(fast.next == null || fast.next.next == null){ return null; } ListNode p1 = pHead; // 頭結點 ListNode p2 = slow; // 相遇結點 while(p1 != p2){ // 相等的時候即p1、p2同時到達相遇結點 p1 = p1.next; p2 = p2.next; } return p1; } }
-
環結點個數
這個問題在我們會求相遇點后已經變得非常簡單。我們讓slow繼續走走走,又走回到相遇點就代表走完了一圈,就能求得環長public class 環結點個數 { public static int nodeNumOfLoop(ListNode head){ ListNode fast = head; ListNode slow = head; int count = 0; // count計數 while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ break; } } if(fast.next == null || fast.next.next == null){ return 0; } ListNode temp = slow; do{ slow = slow.next; count++; } while(slow != temp); return count; } }
-
鏈表長度
看第2題的圖,總長就=c+x嘛!public class 鏈表長度 { public static int lLength(ListNode head){ ListNode p1 = head; int length = 0; if(meetNode(head) == null){ while(p1 != null){ p1 = p1.next; length++; } return length; }else{ return EntryNodeOfLoop(head)+nodeNumOfLoop(head); } } public static ListNode meetNode(ListNode head){ // 相遇點 ListNode slow = head; ListNode fast = head; while(fast.next != null && fast.next.next != null){ slow = slow.next; fast = fast.next.next; if(slow == fast){ return slow; } } return null; } public static int EntryNodeOfLoop(ListNode head){ // 環入口結點距頭結點的距離 int count = 0; ListNode p1 = head; ListNode p2 = meetNode(head); while(p1 != p2){ p1 = p1.next; p2 = p2.next; count++; } return count; } public static int nodeNumOfLoop(ListNode head){ // 環長度(環結點個數) int count = 0; ListNode slow = meetNode(head); ListNode temp = slow; do{ slow = slow.next; count++; } while(slow != temp); return count; } }
總結一下。
第一個,判斷是否有環。轉換成求相遇結點的問題,用快慢指針解決。相遇則有環,反之無環。
第二個,求入口結點(D)。先求相遇結點,然后記住一個結論。頭結點到入口結點的距離(x)與相遇結點到入口結點的距離相同。
第三個,求環結點個數(c)。求相遇結點,然后走一圈,計數。
第四個,求鏈表長度。轉換成求相遇結點+求入口結點的問題。然后x(根據入口結點求得)+c(根據相遇結點求得)
不管三七二十一,相遇結點總是要求的,從一開始拿到問題,就要判斷成不成環,反正要求的!就連入口結點也是根據相遇結點求的。然后其他問題就是求相遇結點和入口結點的變體了。