找题目太麻烦了,为了偷懒,本文中前面7个题目引用自以下文章:
https://www.cnblogs.com/yorkyang/p/10876604.html
不过描述方式并不是照抄,是根据自己的理解从数学原理层面进行了说明
很多同学在面试时,都遇到过链表中的环问题,本文对于链表环的常见问题给出解答,并对原因进行透彻的分析。
先列一下常见的问题:
1.给一个单链表,判断其中是否有环的存在;
2.如果存在环,找出环的入口点;
3.如果存在环,求出环上节点的个数;
4.如果存在环,求出链表的长度;
5.如果存在环,求出环上距离任意一个节点最远的点(对面节点);
6.(扩展)如何判断两个无环链表是否相交;
7.(扩展)如果相交,求出第一个相交的节点;
8.如果存在环,如何遍历输出每个元素?或者换个问法:如何正确释放每个元素?
下面我们对每个问题进行解答:
1.给一个单链表,判断其中是否有环的存在;
这个题目比较常见,很多同学也知道答案:采用“快慢指针”的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,然后在每一步操作中slow向前走一步即:slow = slow->next,而fast每一步向前两步即:fast = fast->next->next。
由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。当两个指针都进入环之后,经过一定步的操作之后二者一定能够在环上相遇。
但是,这个答案的原因大家有没有深究过?
假设slow进入环的入口时,fast刚好循环了N圈,也到了入口,那么会立即相遇。但排除这种特殊情况,假设fast到slow的距离为n,根据小学数学中的速度追赶问题,我们可以很容易的判断出,经过n步,fast和slow一定会相遇。并且n一定小于环的总长,因此,相遇时,slow必然还没有走完一圈。
2.如果存在环,找出环的入口点;
从上面的分析知道,当fast和slow相遇时,slow还没有走完链表。
我们先给出几个术语,方便理解:
L:链表头到入口的距离
R:环的周长
S:slow和fast第一次相遇时的时间,即相遇时slow行走的距离
根据前文,我们可以得到以下结论:
2S-S=R*n:相遇时,fast以及在环中循环了n次,n>=1,可得到S=R*n。
S>=L && S<L+R:相遇时,slow一定没有走完一圈。
如果此时,有2个slow(定为slow1和slow2),一个从起点出发,一个从S位置出发。根据S=R*n,可以推理出,slow1和slow2一定会在S位置再次相遇。如果我们从相遇点往回退,L~S这段肯定是重合的,那么slow1和slow2相遇的第一个点,就必然是L位置。
因此,第二个题目的答案也就出来了:在第一个题目的解题思路,找到第一个相遇点后,设置两个指针,一个从相遇点开始,另一个从起点开始,两者第一次相遇的位置,即为环的入口。
如果想计算L的长度,再让slow从起点走到入口即可得到。
3.如果存在环,求出环上节点的个数;
简单的数数(或者叫遍历),在第一个题目的答案基础上,相遇后,fast指针不动,slow指针继续每次向前走一步,再次与fast重合时行走的距离,即是环的长度。
4.如果存在环,求出链表的长度;
链表总长度=链表头到入口的距离+环的周长。
在第二题与第三题的基础上,计算L+R即可。
5.如果存在环,求出环上距离任意一个节点最远的点(对面节点);
有2个思路:
a.如果已经计算出了环的长度,在指定点向前行R/2即可。
PS:如果R为奇数,向下取整,或者向上取整都可,这两个点都是距离最远的点。
b.如果未计算出环的长度,可以继续使用第一个题目的思路,在指定点设置fast和slow两个指针,当fast完成循环(R为偶数),或者fast=指定点->next(R为奇数),或者fast->next=指定点(R为奇数),此时slow指向的位置即为最远点。
PS:本质上和思路1是一样的,都是走环的一半,只是前提条件不同,使用了不同的方式。
6.(扩展)如何判断两个无环链表是否相交;
有2个思路:
a.暴力破解:对链表1上的每个点,遍历链表2,查找是否有相同。如找到,则判断为相交。
时间复杂度:链表1长度为M,链表2长度为N,则时间复杂度为O(M*N)
b.转换为已知问题,再使用已有的讨论解答:将链表1的尾部指向头部,变成一个新的链表。如果链表2与链表1相交,则链表2就变成了有环链表;如果链表2与链表1不相交,则链表2不变,依然是无环链表。转换后的题目,就变成了求解链表2是否存在环,是不是很熟悉?
时间复杂度:M+N
7.(扩展)如果相交,求出第一个相交的节点;
继承自题目6,同样是2个思路:
a.如果第6题采用了思路a,那么找到相同的位置,即为第一个相交的点。
b.如果第6题采用了思路b,那么就转换成了题目2。
8.如果存在环,如何遍历输出每个元素?或者换个问法:如何正确释放每个元素?
如果按照无环链表的方式进行遍历,当将环循环一次之后,一定会遇到环中的元素循环重复遍历的情况。因此,对于有环链表的遍历,关键还是处理好环的入口指针,保证入口指针及时中断即可。