1, 最簡單的方法, 用一個指針遍歷鏈表, 每遇到一個節點就把他的內存地址(java中能夠用object.hashcode())作爲key放在一個hashtable中. 這樣當hashtable中出現重複key的時候說明此鏈表上有環. 這個方法的時間複雜度爲O(n), 空間一樣爲O(n).java
2, 使用反轉指針的方法, 每過一個節點就把該節點的指針反向:node
<!-- lang: cpp --> Boolean reverse(Node *head) { Node *curr = head; Node *next = head->next; curr->next = NULL; while(next!=NULL) { if(next == head) { /* go back to the head of the list, so there is a loop */ next->next = curr; return TRUE; } Node *temp = curr; curr = next; next = next->next; curr->next = temp; } /* at the end of list, so there is no loop, let's reverse the list back */ next = curr->next; curr ->next = NULL; while(next!=NULL) { Node *temp = curr; curr = next; next = next->next; curr->next = temp; } return FALSE; }
看上去這是一種奇怪的方法: 當有環的時候反轉next指針會最終走到鏈表頭部; 當沒有環的時候反轉next指針會破壞鏈表結構(使鏈表反向), 因此須要最後把鏈表再反向一次. 這種方法的空間複雜度是O(1), 實事上咱們使用了3個額外指針;而時間複雜度是O(n), 咱們最多2次遍歷整個鏈表(當鏈表中沒有環的時候).面試
這個方法的最大缺點是在多線程狀況下不安全, 當多個線程都在讀這個鏈表的時候, 檢查環的線程會改變鏈表的狀態, 雖然最後咱們恢復了鏈表自己的結構, 可是不能保證其餘線程能獲得正確的結果.安全
3, 這是通常面試官所預期的答案: 快指針和慢指針多線程
<!-- lang: cpp --> Boolean has_loop(Node *head) { Node *pf = head; /* fast pointer */ Node *ps = head; /* slow pointer */ while(true) { if(pf && pf->next) pf = pf->next->next; else return FALSE; ps = ps->next; if(ps == pf) return TRUE; } }
須要說明的是, 當慢指針(ps)進入環以後, 最多會走n-1步就能和快指針(pf)相遇, 其中n是環的長度. 也就是說快指針在環能不會跳過慢指針, 這個性質能夠簡單的用概括法來證實. (1)當ps在環中位置i, 而pf在環中位置i-1, 則在下一個iteration, ps會和pf在i+1相遇.
(2)當ps在環中位置i, 而pf在環中位置i-2, 則在下一個iteration, ps在i+1, pf在i, 因而在下一個iteration ps和pf會相遇在i+2位置
(3)和上面推理過程相似, 當ps在i, pf在i+1, 則他們會通過n-1個iteration在i+n-1的位置相遇. 因而慢指針的步數不會超過n-1.oop
擴展:線程
這個問題還有一些擴展, 例如, 如何找到環的開始節點? 如何解開這個環? 這些問題的本質就是如何找到有"回邊"的那個節點.指針
咱們能夠利用方法3的一個變形來解決這個問題:code
<!-- lang: cpp --> Boolean has_loop(Node *head) { Node *pf = head; /* fast pointer */ Node *ps = head; /* slow pointer */ while(true) { /* step 1, is there a loop? */ if(pf && pf->next) pf = pf->next->next; else return FALSE; ps = ps->next; if(ps == pf) break; } /* step 2, how long is the loop */ int i = 0; do { ps = ps->next; pf = pf->next->next; i++; } while(ps!=pf) /* step 3, use 2 addtional pointers with distance i to break the loop */ ps = head; pf = head; int j; for(j=0; j<i; j++) { pf = pf->next; } j = 0; while(ps!=pf) { ps = ps->next; pf = pf->next; j++; } printf("loop begins at position %d, node address is %x", j, ps); /*step 4, break the loop*/ for(j=0; j<=i; j++) { ps = ps->next; } //step i-1 in the loop from loop beginning ps->next = NULL; // break the loop return TRUE; }