zoukankan      html  css  js  c++  java
  • 关于有环单链表的那些事儿

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

      如下图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;

  • 相关阅读:
    oc73--NSArray使用
    oc72--NSArray排序
    oc71--NSArray2
    oc70--NSArray1
    oc69--NSMutableString
    oc68--NSString
    oc67--NSString1
    驱动人生病毒处理的简单方法(20191207)
    CentOS 安装libgdi的方法
    局域网Linux机器中病毒简单处理 .aliyun.sh 挖矿病毒 ---不彻底
  • 原文地址:https://www.cnblogs.com/jiabei521/p/4272009.html
Copyright © 2011-2022 走看看