判斷鏈表是否有環


轉自dancingrain判斷鏈表中是否有環 ----- 有關單鏈表中環的問題

 

  首先,關於單鏈表中的環,一般涉及到一下問題:

  1.給一個單鏈表,判斷其中是否有環的存在;

  2.如果存在環,找出環的入口點;

  3.如果存在環,求出環上節點的個數;

  4.如果存在環,求出鏈表的長度;

  5.如果存在環,求出環上距離任意一個節點最遠的點(對面節點);

  6.(擴展)如何判斷兩個無環鏈表是否相交;

  7.(擴展)如果相交,求出第一個相交的節點;

1、判斷單鏈表是否有環

  思路:

  1. 暴力:給定一個足夠大的循環上限,遍歷鏈表,遍歷到空指針則沒有環,到達循環上限則認為有環。
  2. 做標記:將訪問過的節點做上標記,每次訪問新的節點都先看看該節點是否帶標記。兩種標記方法,一是修改節點的值為某個特定值,來表示已經訪問過了;二是將訪問過的節點存入set。
  3. 快慢指針:快指針一次走兩步,慢指針一次走一步。
 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.轉化為環的問題。我們將一個鏈表的頭尾相連,然后從另一個鏈表出發判斷是否有環。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM