本题是苦主找实习的面试真题,想了半天,想出一个暴力解法:
1.首先定义一个哈希集合hS,其val为ListNode。【面试的时候把哈希集合忘了,用的是数组……】
2.遍历链表,判断
1st.哈希集合中,是否存在该结点。存在就说明有环,没什么好说的。
2nd.如果不存在,把结点添加进hS。
苦主痛定思痛,一下面试就立马百度答案,发现用的竟然是快慢指针。
苦主愣住了,竟然还是经典的链表三板斧:栈、快慢指针、哑节点之一啊!
题目定义:
1.链表:本题链表为单链表,链表头节点存在数据,保证无特殊情况。
2.父辈节点的定义:对于链表a->b->c->d,如果认为a是b、c、d的父辈(或祖宗辈)节点。
3.环:存在一个节点X,它指向其父辈(或祖宗辈)结点,说明有环。【本质就是,有一个节点存在两个前驱节点(当然,如果是头节点,仍是一个,所以这个规律用于判断环时,一致性较差,需要额外讨论)
4.环的性质:我们可以发现,一旦链表中存在环,那么链表不会出现一个节点D,其下一个节点为null,也就是D.next!=null。
快慢指针的定义和额外问题:
1.快慢指针:定义两个指针fast和slow,fast每次移动的“距离”长于slow,这就是快慢指针。
问题1:如何判断是否有环?
判断有无,使两个指针从头节点出发,令fast每次走两步,slow每次走一步。最终,如果fast指向null,说明无环;;如果fast与slow相遇,说明一定有环【朴素的理解是:既然fast走得那么快,在单向的道路上,slow怎么能赶上fast呢?只有类似操场,slow才能赶上fast,所以才会相遇】
证明:fast与slow的关系可以证明有无环。
1.无环链表:最终fast一定指向null,两者互为充分必要条件,不在此说明。
2.有环链表:其难点,主要是“为什么一定相遇?有没有可能会错开?”
证明:
如果我们把环链表看成一个长度为n的数组,如果遍历结束,指针指向下标为0的数据,如图:
对于有环链表,我们可以看到,最终fast一定会落在slow的“后面”,此时fast和slow可能差距N个格子。
1.差距N个格子:比如fast指向1,slow指向9,此时差距8格,fast进一步,指向3,slow进一步,指向10,此时差距7格,我们甚至可以把数组延长,即12后加上0、1、2……【也可能环从4、5开始】,可以发现,二者越来越近。总之,由于fast每次走得比slow多一格,因此最终一定会差距在2个格子。
2.差距2个格子:比如fast指向1,slow指向3,此时差距两格,fast进一步,刚好指向3,slow进一步,指向4。此时差距一个格子。
3.差距1个格子:比如fast指向3,slow指向4,fast进一步,指向5,slow进一步,指向5,二者相等,此时证明有环。
问题2:如何得到入环点?
入环点:从链表的角度说,即有两个前驱节点的节点【也可能是有一个前驱节点的头节点】
知道有环,无法解决环路问题,需要拿到最末尾的节点,使其指向null,才能解决。
借助一个新指针ptr,从头节点开始,每次进一位,同时使slow每次进一位,最终slow与ptr重合的地方,即入环点。
证明:为什么slow继续走,和ptr重合时,就是入环点?
假设该链表为0->1->2->3->4->5->6->2->3->4……即下图:
目前看不出什么,我们首先要确定的,是fast要经过多少步,才能和slow相遇?
如果把环看成一个操场,fast走的速度是slow的两倍,同样时间,fast走的距离也是slow的两倍,因此,fast至少走完一个环,slow最多走完一个环。
证明:如果链表为0->1->2->3->4->0->1->2……即指向头节点的环,fast、slow同步出发,那么有以下步骤:
1.fast指向2,slow指向1;;
2.fast指向4,slow指向2;;
3.fast指向1,slow指向3;;(fast走完第一个环)
4.fast指向3,slow指向4;;
5.fast指向0,slow指向0;;(fast走完第二个环,slow走完第一个环)
如图,对于入环点是头节点head的链表,如果把a到b的距离S,看成环的长度,那么slow相当于从a'出发(也就是第二圈)
我们已经证明了,在有环时,fast与slow的距离不断缩近。并且每次接近一格,不会发生slow就在面前,却直接略过的情况。
所以,对于入环点是其它节点【非头节点】的链表,如果把头节点到入环点的距离设为HI,那么fast相当于提前了HI的距离,slow绝对走不完第一圈,就会被赶上。
如图:(该图理解为H……后面接着a,勉强看吧)
此时,ptr与入环点a距离HI,如果slow入环后,走了dealt步与fast相遇,那么slow走了dealt+HI步,如果一个环的长度是X,那么此时slow距离入环点长度为X-dealt;
如此,只需证明HI与X-dealt大小成倍数即可。【HI=kX-dealt,k>=1】
设fast走了n圈,那么fast一共走了HI+nX+dealt;
又因为fast走的总距离,是slow的两倍(因为fast进二,slow进一)
所以,2*(HI+dealt)=HI+nX+dealt;
消除,得到HI=nX-dealt;
如此,如果slow继续走HI步,就会走nX圈,外加dealt步,刚好到达入环点。
并且,ptr走了HI步,刚好到达入环点。
证明结束。
以上内容即我想分享的关于快慢指针的一些知识。
我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。