上個月去CVTE面試安卓工程師時,面試官問了一道關於鏈表的算法問題,判斷一個單鏈表中是否有環,當時我沒仔細思考,沒考慮到可能有子環的。java
首先鏈表結點聲明以下:面試
struct ListNode { int key; ListNode * next; };
思路:若是一個單鏈表中有環,用一個指針去遍歷,永遠不會結束,因此能夠用兩個指針,一個指針一次走一步,另外一個指針一次走兩步,若是存在環,則這兩個指針會在環內相遇,時間複雜度爲O(n)。算法
bool HasCircle(ListNode * pHead) { ListNode * pFast = pHead; // 快指針每次前進兩步 ListNode * pSlow = pHead; // 慢指針每次前進一步 while(pFast != NULL && pFast->next != NULL) { pFast = pFast->next->next; pSlow = pSlow->next; if(pSlow == pFast) // 相遇,存在環 return true; } return false; }
用java試下,由於java是沒有指針的,因此須要改動一下。spa
/* *單鏈表的結點類 */ class LNode{ //爲了簡化訪問單鏈表,結點中的數據項的訪問權限都設爲public public int data; public LNode next; }
public class LinkListUtli { public static boolean hasCircle(LNode L) { LNode slow=L;//slow表示從頭結點開始每次日後走一步的指針 LNode fast=L;//fast表示從頭結點開始每次日後走兩步的指針 while(fast!=null && fast.next!=null) { if(slow==fast) return true;//p與q相等,單鏈表有環 slow=slow.next; fast=fast.next.next; } return false; } }
拓展問題1:若是單鏈表有環,找出環的入口節點(環的鏈接點)。指針
這裏先證實一個定理:碰撞點到鏈接點的距離=頭指針到鏈接點的距離code
假設單鏈表的總長度爲L,頭結點到環入口的距離爲a,環入口到快慢指針相遇的結點距離爲x,環的長度爲r,慢指針總共走了s步,則快指針走了2s步。另外,快指針要追上慢指針的話快指針至少要在環裏面轉了一圈多(假設轉了n圈加x的距離),獲得如下關係:
s = a + x
2s = a + nr + x
=>a + x = nr
=>a = nr - xblog
由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後必定相遇在環入口結點。ci
public class LinkListUtli { //當單鏈表中沒有環時返回null,有環時返回環的入口結點 public static LNode searchEntranceNode(LNode L) { LNode slow=L;//p表示從頭結點開始每次日後走一步的指針 LNode fast=L;//q表示從頭結點開始每次日後走兩步的指針 while(fast !=null && fast.next !=null) { if(slow==fast) break;//p與q相等,單鏈表有環 slow=slow.next; fast=fast.next.next; } if(fast==null || fast.next==null) return null; slow=L; while(slow!=fast) { slow=slow.next; fast=fast.next; } return slow; } }
拓展問題2:求環的長度。同步
讓指針指向入口節點,遍歷直到回到入口節點,走過的長度即環的長度。ast
//求單鏈表環的長度 public static int circleLength(LNode L) { LNode p=searchEntranceNode(L);//找到環的入口結點 if(p==null) return 0;//不存在環時,返回0 LNode q=p.next; int length=1; while(p!=q) { length++; q=q.next; } return length;//返回環的長度 }
思惟擴展:這裏用到了快慢指針來判斷單鏈表是否有環,快慢指針還能快速解決其餘鏈表問題。
一:已知單鏈表的頭指針,查找到倒數第K個節點
這道題的通俗的解法就是先遍歷一邊鏈表,獲得鏈表的長度N,而後再從頭開始遍歷N-K個節點便可
可是如今若是要求只遍歷一遍鏈表的話,該怎麼操做呢?
這時候就能夠藉助快指針和慢指針了
咱們定義一個快指針P和慢指針Q,先讓P指針走到K個節點位置,而後Q指針從頭指針開始和P一塊兒移動,當P移動到尾部的時候,那麼此時Q節點所在的位置就是倒數第K個節點。
二:已知單鏈表的頭結點,查找到鏈表的中間節點
這道題的通俗的解法和上面的方法同樣,就是先遍歷一邊鏈表,獲得鏈表的長度N,而後再次遍歷N/2個節點便可
可是如今一樣的若是要求之遍歷一邊鏈表的話,該怎麼操做呢?
這時候咱們一樣能夠藉助快指針和慢指針了
咱們定義一個快指針P和慢指針Q,P和Q同時從頭指針出發,快指針P每次移動兩步,慢指針每次移動一步,當快指針P到尾部的時候,慢指針Q所在的位置就是中間節點的位置。