给定一个有环链表,实现一个算法返回环路的开头结点
2017-04-17 20:25
381 查看
检测链表是否存在环路
有一种简单的做法叫FastRunner/SlowRunner法。FastRunner一次移动两步,而SlowRunner一次移动一步。这就好比两辆赛车绕着同一条赛道以不同的速度前进,最终必然会碰到一起。聪明的读者可能会问:FastRunner会不会刚好“越过”SlowRunner,而不发生碰撞呢?绝无可能。假设FastRunner真的越过了SlowRunner,且SlowRunner处于位置i,FastRunner位于位置i+1。那么,在前一步,SlowRunner就处于位置i-1,FastRunner处于位置((i+1)-2)或i-1。也就是说,两者碰在一起了。
什么时候碰在一起?
假定这个链表有一部分不存在环路,长度为k。若运用上述算法,FastRunner和SlowRunner什么时候会碰在一起呢?
我们知道,SlowRunner每走p步,FastRunner就会走2p步。因此,当SlowRunner走了k步进入环路部分时,FastRunner已走了总共2k步,进入环路部分已有2k-k步或k步。由于k可能比环路长度大得多,实际上我们应该把它写作mod(k, LOOP_SIZE)步,并用K表示。
对于之后的每一步,FastRunner和SlowRunner之间不是走远一步就是更近一步,具体要看观察的角度。也就是说,因为两者处于圆圈中,当A以远离B的方向走出q步时,同时也是向B更近了q步。综上,我们得出以下几点:
(1)SlowRunner处于环路中的0步位置;
(2)FastRunner处于环路中的K步位置;
(3)SlowRunner落后于FastRunner,相距K步;
(4)FastRunner落后于SlowRunner,相距LOOP_SIZE-K步;
(5)每过一个单位时间,FastRunner就会更接近于SlowRunner一步。
那么,两个节点什么时候相遇?若FastRunner落后于SlowRunner,相距LOOP_SIZE-K步,并且每经过一个单位时间,FastRunner就走近SlowRunner一步,那么,两者将在LOOP_SIZE-K步之后相遇。此时,两者与环路起始处相距K步,我们将这个位置称为CollisionSpot。
如何找到环路起始处?
现在我们知道CollisionSpot与环路起始处相距K个节点。由于K=mod(k, LOOP_SIZE)(或者换句话说,k=K+M*LOOP_SIZE,其中M为任意整数),也可以说,CollisionSpot与环路起始处相距k个节点。例如,若有个环路长度为5个节点,有个节点N处于距离环路起始处2个节点的地方,我们也可以换个说法:这个节点处于距离环路起始处7个、12个甚至397个节点。至此,CollisionSpot和LinkedListHead与环路起始处均相距k个节点。
现在,若用一个指针指向CollisionSpot,用另一个指针指向LinkedListHead,两者与LoopStart均相距k个节点。以同样的速度移动,这两个指针会再次碰在一起——这次是在k步之后,此时两个指针都指向LoopStart,然后只需返回该结点即可。
将全部整合在一起
总结一下,FastPointer的移动速度是SlowPointer的两倍。当SlowPointer走了k个节点进入环路时,FastRunner已进入链表环路k个节点。也就是说FastRunner和SlowRunner相距LOOP_SIZE-k个节点。接下来,若SlowPointer每走一个节点,FastRunner就走两个节点,每走一次,两者的距离就会更近一个节点。因此,在走了LOOP_SIZE-k次后,它们就会碰在一起。这时两者距离环路起始处就有k个节点。
链表首部与环路起始处也相距k个节点。因此,若其中一个指针保持不变,另一个指针指向链表首部,则两个指针就会在环路起始处相会。
根据之前的4部分描述,就能直接导出下面的算法。
(1)创建两个指针:FastPointer和SlowPointer。
(2)SlowPointer每走一步,FastPointer就走两步。
(3)两者碰在一起时,将SlowPointer指向LinkedListHead,FastPointer保持不变。
(4)以相同速度移动SlowPointer和FastPointer,一次一步,然后返回新的碰撞处。
下面给出算法代码:
LinkedListNode findBegining(LinkedListNode head) { LinkedListNode slow = head; LinkedListNode fast = head; /*找出碰撞处,将处于链表中LOOP_SIZE-k步的位置*/ while(fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if(slow == fast) { // 碰撞 break; } } /*错误检查,没有碰撞处,也即没有回路*/ if(fast == null || fast.next == null) { return null; } /*将slow指向首部,fast指向碰撞处,两者距离环路 *起始处k步,若两者以相同速度移动,则必定会在环 *路起始处碰在一起*/ slow = head; while(slow != fast) { slow = slow.next; fast = fast.next; } /*至此两者均指向环路起始处*/ return fast; }
相关文章推荐
- 9.2链表(六)——给定一个有环链表,实现一个算法返回环路的开头结点
- 【Java】给定一个有环链表,实现算法返回环路的开头结点
- 给定一个有环链表,实现一个算法返回环路的开头结点
- 程序员面试金典: 9.2链表 2.6给定有环链表,实现算法返回环路的开头节点
- 010给定一个循环链表,实现一个算法返回这个环的开始结点 (keep it up)
- 给定一个循环链表,实现一个算法返回这个环的开始结点
- 链式A+B有两个用链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表的首部。编写函数对这两个整数求和,并用链表形式返回结果。 给定两个链表ListNode* A,ListN
- 编写算法实现建立一个带头结点的含n个元素的双向循环链表H,并在链表H中的第i个位置插入一个元素e
- 有一棵二叉树,请设计一个算法,按照层次打印这棵二叉树。 给定二叉树的根结点root,请返回打印结果,
- 请实现一个算法,确定一个字符串的所有字符是否全都不同。这里我们要求不允许使用额外的存储结构。 给定一个string iniString,请返回一个bool值,True代表所有字符全都不同,False代
- 请实现一个算法,在不使用额外数据结构和储存空间的情况下,翻转一个给定的字符串(可以使用单个过程变量)。 给定一个string iniString,请返回一个string,为翻转后的字符串。保证字符串的
- 输入一组整型元素序列,使用尾插法建立一个带有头结点的单链表。 ② 实现该线性表的遍历。 ③ 在该单链表的第i个元素前插入一个整数。 ④ 删除该单链表中的第i个元素,其值通过参数将其返回。 ⑤ 建立两个
- 有一堆扑克牌,其中某张牌的张数超过了扑克牌总数的一半,请找到这张牌。写出算法思路、代码实现和算法的时间复杂度,要求算法尽可能高效。假设给定一个扑克牌的数组poker和它的大小n,请返回所求的扑克牌。
- 原串翻转 请实现一个算法,在不使用额外数据结构和储存空间的情况下,翻转一个给定的字符串(可以使用单个过程变量)。 给定一个string iniString,请返回一个string,为翻转后的字符串
- 给定一棵二叉树,设计一个算法,创建含有某一深度上所有结点的链表(比如:若一棵树的深度为D,则会创建出D个链表)
- 给定一个单向链表(长度未知),请设计一个既节省时间又节省空间的算法来找出该链表中的倒数第m个元素。实现这个算法,并为可能出现的特例情况安排好处理措施。“倒数第m个元素”是这样规定的:当m=0时,链表的
- 链表分割 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 给定一个链表的头指针 ListNode* pHead,请返回重新排列后的链表的头指针。注意:分割以
- 有一个带头结点的单链表L={a1,b1,a2,b2,...,an,bn},设计一个算法将其拆分成两个带头结点的单链表A和B,正序链表A={a1,a2,a3...,an},逆序链表B={bn,bn-1,
- 判断一个链表是否有环,如果有环返回环开始的结点指针
- 018给定二叉查找树的一个结点, 写一个算法查找它的“下一个”结点“(keep it up)