单链表环相关

2020-10-16  本文已影响0人  Djbfifjd

一、单链表问题

1️⃣给一个单链表,判断是否存在环。
2️⃣如果存在环,找出环的入口点。
3️⃣如果存在环,求出环上节点的个数。
4️⃣如果存在环,求出链表的长度。
5️⃣如果存在环,求出环上距离任意一个节点最远的点(对面节点)。
6️⃣如何判断两个无环链表是否相交。如果相交,求出第一个相交的节点。

二、判断时候有环(链表头指针为head)

1️⃣“快慢指针”法。就是有两个指针 fast 和 slow,开始的时候两个指针都指向链表头head,然后在每一步操作中 slow 向前走一步即:slow = slow->next,而 fast 每一步向前走两步即:fast = fast->next->next。由于 fast 要比 slow 移动的快,如果有环,fast 一定会先进入环,而 slow 后进入环。当两个指针都进入环之后,经过一定步数的操作之后,二者一定能够在环上相遇,并且此时 slow 还没有绕环一圈(相当于田径跑道上套圈的逻辑)。如图:

当 slow 刚进入环时每个指针可能处于上面的情况,接下来 slow 和 fast 分别向前走即:

if (slow != NULL && fast->next != NULL) { 
         slow = slow -> next ; 
         fast = fast -> next -> next ;
}

也就是说,slow 每次向前走一步,fast 向前追了两步,因此每一步操作后 fast 到 slow 的距离缩短了1步,这样继续下去就会使得两者之间的距离逐渐缩小:...、5、4、3、2、1、0 -> 相遇。又因为在同一个环中 fast 和 slow 之间的距离不会大于换的长度,因此到二者相遇的时候 slow 一定还没有走完一周(或者正好走完以后,这种情况出现在开始的时候 fast 和 slow 都在环的入口处)。

typedef struct node{ 
    char data ; 
    node * next ; 
}Node; 
bool exitLoop(Node *head) 
{ 
    Node *fast, *slow ; 
    slow = fast = head ; 
   
    while (slow != NULL && fast -> next != NULL) 
    { 
        slow = slow -> next ; 
        fast = fast -> next -> next ; 
        if (slow == fast) 
            return true ; 
    } 
    return false ; 
}

2️⃣使用 Set 做标记,一次遍历,当发现这个节点被标记了,那么说明走到了重复的点,也就说明有环了。

三、找出环的入口点

从上面的分析知道,当 fast 和 slow 相遇时,slow 还没有走完链表,假设 fast 已经在环内循环了n(1<= n)圈。此时 slow 走了s步,则 fast 走了2s步,又由于 fast 走过的步数为s + n*r(s + 在环上多走的n圈),则有等式:
2*s = s + n*r--->②s = n*r
假设起点到入口点的距离是a,入口点到相遇点的距离是x,则有:
a + x = s = n*r--->④a = n*r - x

结合④以及上图可以看出,从链表起点 head 开始到入口点的距离a,与从入口点到 slow 和 fast 的相遇点的距离x相等。【分析】现让一指针p1从链表起点 head 开始遍历,指针p2从相遇点开始遍历,且p1和p2移动步长均为1。则当p1移动a步即到达环的入口点,由④可知,此时p2也已移动a步即nr - x步。由于p2是从相遇点开始移动,故p2移动nr步是回到了相遇点,再退x步则是到了环的入口点。也即,当p1移动a步第一次到达环的入口点时,p2也恰好到达了该入口点。

因此可以用指针(ptr1,prt2),同时从 head 与 slow 和 fast 的相遇点出发,每一次操作走一步,直到 ptr1 == ptr2,此时的位置也就是入口点。

Node* findLoopStart(Node *head) 
{ 
    Node *fast, *slow ; 
    slow = fast = head ; 
   
    while (slow != NULL && fast -> next != NULL) 
    { 
        slow = slow -> next ; 
        fast = fast -> next -> next ; 
        if (slow == fast) break ; 
    } 
    if (slow == NULL || fast -> next == NULL) return NULL ; //没有环,返回NULL值 
   
    Node * ptr1 = head ; //链表开始点 
    Node * ptr2 = slow ; //相遇点 
    while (ptr1 != ptr2)  
    { 
        ptr1 = ptr1 -> next ; 
        ptr2 = ptr2 -> next ; 
    } 
    return ptr1 ; //找到入口点 
} 

四、求环上节点的个数

1️⃣记录下相遇节点存入临时变量 tempPtr,然后让 slow (或者 fast 也行)继续向前走slow = slow -> next,一直到slow == tempPtr,此时经过的步数就是环上节点的个数。

2️⃣从相遇点开始 slow 和 fast 继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次相遇,此时经过的步数就是环上节点的个数 。

对于第二种思路,可以这样想,结合上面的分析,fast 和 slow 每一次操作都会使得两者之间的距离较少1。可以把两者相遇的时候看做两者之间的距离正好是整个环的长度r。因此,当再次相遇的时候所经过的步数正好是环上节点的数目。

五、求出链表的长度

链表长度L = 起点到入口点的距离 + 环的长度r

六、求出环上距离任意一个节点最远的点(对面节点)

如图,点1和4、点2和5、点3和6分别互为“对面节点”,也就是环上最远的点,要求是怎么求出环上任意一个点的最远点。

对于环上任意的一个点 ptr0,要找到它的“对面点”,可以这样思考:同样使用快慢指针法,让 slow 和 fast 都指向 ptr0,每一步都执行与上面相同的操作(slow 每次跳一步,fast 每次跳两步),当fast = ptr0或者fast = prt0->next的时候 slow 所指向的节点就是 ptr0 的”对面节点“。

为什么是这样呢?可以这样分析:

如图,把环从 ptr0 处展开,展开后可以是无限长的(如上在6后重复前面的内容)。由于 slow 移动的距离永远是 fast 的一半,因此当 fast 遍历完整个环长度r个节点的时候 slow 正好遍历了r/2个节点,也就是说此时正好指向距离 ptr0 最远的点。

七、如何判断两个无环链表是否相交,如果相交,求出第一个相交的节点

做个转化:假设有两个链表 listA 和 listB,如果两个链表都无环,并且有交点,那么可以让其中一个链表(比如listA)的尾节点连接到其头部,这样在 listB 中就一定会出现一个环。因此这个问题就转化成了问题1️⃣和2️⃣。看看下图就会明白了:
上一篇下一篇

猜你喜欢

热点阅读