关于有环单链表的那些事儿

  关于有环单链表,即单链表中存在环路,该问题衍生出很多面试题,特在此汇总,方便查阅也帮助自己梳理下思路。

  如下图1所示为有环单链表,假设头结点为H, 环的入口点为A。

  

  关于有环单链表主要有几个问题:

  • 该单链表中是否真有环存在?
  • 如何求出环状的入口点?
  • 如何求出环状的长度?
  • 求解整条链表的长度?

 

该单链表中是否真有环存在?

  首先,关于第一个问题,如何确定一条链表中确实存在环,关于环状的检测主要有三种方法,链表环状检测主要有三种方法:外部记录法,内部记录法以及追赶法。

  内部标记法和外部标记法其实是一个道理,不过就是辅助变量一个是在链表节点内,一个是借助辅助数组或者hash或者AVL,红黑树等 把已经访问过的节点地址存起来,每次访问下一个节

点的时候进行查询看是否已经出现过。这里不再赘述。主要看追赶法,也称快满指针法,而追赶法大家一定都已经烂熟于心了。

  追赶法主要利用最大公倍数原理,2个游标,对链表进行访问,例如:pSlow, pFast。 pSlow访问每步向前进1个节点,而pFast则每次向前前进2个节点,如果有环则pSlow和pFast必会相

遇,如果pFast最终指向了NULL,则说明该链表不存在环路。因为两个指针步子迈的不一样,因为被称作快慢指针。

  

  代码如下(C++实现):

 1 // Definition for singly - linked list.
 2 struct ListNode {
 3     int val;
 4     ListNode *next;
 5     ListNode(int x) : val(x), next(nullptr) {}
 6 };
 7 
 8 bool isLoopList(ListNode *pHead){
 9     if (nullptr == pHead || nullptr == pHead->next){
10         return false;
11     }
12 
13     ListNode *pSlow = pHead;
14     ListNode *pFast = pHead;
15     while (pFast && pFast->next){
16         pFast = pFast->next->next;
17         pSlow = pSlow->next;
18 
19         if (pFast == pSlow){
20             break;
21         }
22     }
23 
24     return !(nullptr == pFast || nullptr == pFast->next);
25 }

 

 

如何求出该有环单链表的环的入口?

  关于这个问题,首先我们需要证明当pSlow和pFast第一次相遇的时候,pSlow并未走完整个链表或者恰好到达环入口点。

  看图,假设pSlow到达环状入口点A的时候,pFast在环上某一点B,假设B逆时针方向离A点距离为y,并且整个环状的长度为R,我们知道y <= R。从A点开始,pSlow向前走y步,此时pFast从点B往前则走2 * y步 并与pSlow相遇于点D,此时pSlow还需R - y 才能到达链表尾端,也即A点。因为y <= R,因此R - y >= 0。得证。

 

  

  此时我们假设相遇的时候pSlow走了s步,那pFast走2 * s步,而pFast多走的肯定是在环内绕圈,很自然我们有 

  2 * s = s + n * R ;   (n >= 1)

  有: s = n * R (n >= 1).

  设整个链表的长度为L, HA的长度为a ,第一次相遇点B与A的距离为x,则有

  a + x = s = n * R;

  a + x = (n - 1 + 1 ) * R = (n - 1) * R + R =  (n - 1) * R + L - a;

  有 a = (n - 1) * R + (L - a - x);  有前面我们证明知,L - a - x 为我们所设变量y。 因此a = (n - 1) * R + y; (n >= 1).

  这样,我们就可以这样,相遇点设置一个指针,链表头部设置一个指针,这两个指针同时按照一步一个节点前进,第一次相遇的时候必定是相遇点指针走y + (n - 1)*R的时候,也就是入

口点A的位置。得证,因此获取入口点的实现如下。

 1     ListNode *loopJoint(ListNode *pHead){
 2         if (nullptr == pHead || nullptr == pHead->next){
 3             return nullptr;
 4         }
 5 
 6         ListNode *pSlow = pHead;
 7         ListNode *pFast = pHead;
 8         while (pFast && pFast->next){
 9             pFast = pFast->next->next;
10             pSlow = pSlow->next;
11 
12             if (pFast == pSlow){
13                 break;
14             }
15         }
16 
17         if (nullptr == pFast || nullptr == pFast->next){
18             return nullptr;
19         }
20 
21         // 此时调整两个指针为普通指针,一次一步,并且其中一个指针从头部开始,第一次相遇点一定是环的入口点
22         pSlow = pHead;
23         while (pFast != pSlow){
24             pFast = pFast->next;
25             pSlow = pSlow->next;
26         }
27 
28         return pSlow;
29     }

 

如何求出该有环单链表中环的长度?

  有了以上的基础,环状的长度就很明显了,入口点已知,沿着环状走一圈即得。

 

如何求出该有环单链表长度?

  同理,当R已知,而问题2中其中指向头部的指针到达环状入口点的时候HA已知,因此单链表的长度等于 L =  R + a;

 

posted @ 2015-02-04 12:01  菜鸟加贝的爬升  阅读(448)  评论(0编辑  收藏  举报