轉自dancingrain判斷鏈表中是否有環 ----- 有關單鏈表中環的問題
首先,關於單鏈表中的環,一般涉及到一下問題:
1.給一個單鏈表,判斷其中是否有環的存在;
2.如果存在環,找出環的入口點;
3.如果存在環,求出環上節點的個數;
4.如果存在環,求出鏈表的長度;
5.如果存在環,求出環上距離任意一個節點最遠的點(對面節點);
6.(擴展)如何判斷兩個無環鏈表是否相交;
7.(擴展)如果相交,求出第一個相交的節點;
1、判斷單鏈表是否有環
思路:
- 暴力:給定一個足夠大的循環上限,遍歷鏈表,遍歷到空指針則沒有環,到達循環上限則認為有環。
- 做標記:將訪問過的節點做上標記,每次訪問新的節點都先看看該節點是否帶標記。兩種標記方法,一是修改節點的值為某個特定值,來表示已經訪問過了;二是將訪問過的節點存入set。
- 快慢指針:快指針一次走兩步,慢指針一次走一步。
1 struct ListNode{ 2 int val; 3 ListNode *next; 4 5 ListNode(int x) : val(x), next(nullptr) { } 6 }; 7 8 // 1暴力 9 #define MAXTIMES 10000 10 bool hasCycle1(ListNode *head) 11 { 12 ListNode *p = head; 13 int i = 0; 14 while ((i < MAXTIMES) && (p != nullptr)) 15 { 16 p = p->next; 17 i++; 18 } 19 20 return p != nullptr; 21 } 22 23 24 // 2做標記 25 #include <set> 26 bool hasCycle2(ListNode *head) 27 { 28 std::set<ListNode*> marked; 29 ListNode *p = head; 30 31 while (p) 32 { 33 if(marked.find(p) != marked.end()) 34 return true; 35 else 36 { 37 marked.insert(p); 38 p = p->next; 39 } 40 } 41 42 return false; 43 } 44 45 // 3快慢指針 46 bool hasCycle3(ListNode *head) 47 { 48 ListNode *fast, *slow; 49 fast = slow = head; 50 51 while (slow and fast and fast->next) 52 { 53 slow = slow->next; 54 fast = fast->next->next; 55 if (slow == fast) 56 return true; 57 } 58 return false; 59 }
2、如果存在環,找出環的入口點
從上面的分析知道,當fast和slow相遇時,slow還沒有走完鏈表,假設fast已經在環內循環了n(1<= n)圈。假設slow走了S步,因為fast每次走2步,則fast走了2*S步,又由於
fast走過的步數 = S + n * R(S + 在環上多走的n圈),則有下面的等式:
2 * S= S + n * R ;
=> S = n * R;
如果假設整個鏈表的長度是L,入口和相遇點的距離是x(如上圖所示),起點到入口點的距離是a(如上圖所示),則有:
a + x = S = n * R;
a + x = (n - 1) * R + R = (n - 1) * R + (L - a)
=> a = (n - 1) * R + (L -a -x)
結論:從鏈表起點head開始到入口點的距離a,與從slow和fast的相遇點到入口點的距離加上若干個環的長度之和相等。換句話說,如果有兩個指針p1, p2分別從head和相遇的點出發,他們最終一定會在環入口點相遇。
1 struct ListNode{ 2 int val; 3 ListNode *next; 4 5 ListNode(int x) : val(x), next(nullptr) { } 6 }; 7 8 9 // 快慢指針 10 ListNode* cycleEnterPoint(ListNode *head) 11 { 12 ListNode *fast, *slow; 13 fast = slow = head; 14 // 尋找相遇點 15 ListNode *meetPoint = nullptr; 16 while (slow and fast and fast->next) 17 { 18 slow = slow->next; 19 fast = fast->next->next; 20 if (slow == fast) 21 { 22 meetPoint = slow; 23 break; 24 } 25 } 26 // 尋找入口點 27 ListNode *p1 = head; 28 ListNode *p2 = meetPoint; 29 while (p1 != nullptr and p2 != nullptr and p1 != p2) 30 { 31 p1 = p1->next; 32 p2 = p2->next; 33 } 34 35 return p1; 36 }
3、如果存在環,求出環上節點的個數
思路:
1.記錄下相遇節點存入臨時變量tempPtr,然后讓slow(或者fast,都一樣)繼續向前走slow = slow -> next;一直到slow == tempPtr; 此時經過的步數就是環上節點的個數。
2.從相遇點開始slow和fast繼續按照原來的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次相遇,此時經過的步數就是環上節點的個數 。
4、如果存在環,求出鏈表的長度
思路:
1.鏈表長度L = 起點到入口點的距離 + 環的長度r。
2.對指針走過的節點做標記,到找到一個重復訪問的節點為止,該指針走過的距離就是鏈表長度。
5、如果存在環,求出環上距離任意一個節點最遠的點(對面節點)
思路:
對於換上任意的一個點ptr0, 我們要找到它的”對面點“,可以這樣思考:同樣使用上面的快慢指針的方法,讓slow和fast都指向ptr0,每一步都執行與上面相同的操作(slow每次跳一步,fast每次跳兩步),當fast = ptr0或者fast = prt0->next的時候slow所指向的節點就是ptr0的“對面節點”。
6、(擴展)如何判斷兩個無環鏈表是否相交,如果相交,求出第一個相交的節點
思路:
1.利用鏈表長度差。
2.轉化為環的問題。
1.利用鏈表長度差。讓較長的鏈表將它多出來的那么部分長度先走完,讓后兩個鏈表再一起往前走,知道相遇或者各自都走完。
2.轉化為環的問題。我們將一個鏈表的頭尾相連,然后從另一個鏈表出發判斷是否有環。