1, 最簡單的方法, 用一個指標遍曆鏈表, 每遇到一個節點就把他的記憶體位址(java中可以用object.hashcode())做為key放在一個hashtable中. 這樣當hashtable中出現重複key的時候說明此鏈表上有環. 這個方法的時間複雜度為O(n), 空間同樣為O(n).
2, 使用反轉指標的方法, 每過一個節點就把該節點的指標反向:
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, 這是一般面試官所預期的答案: 快指標和慢指標
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.
擴充:
這個問題還有一些擴充, 例如, 如何找到環的開始節點? 如何解開這個環? 這些問題的本質就是如何找到有"回邊"的那個節點.
我們可以利用方法3的一個變形來解決這個問題:
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;
}