证明利用快慢指针寻找有环单链表中环的起点算法
2016-01-07 20:39
316 查看
问题:给定一个有环单链表,找到链表中环的起点,也就是说,找到下图中的单链表中Join点:
(本图来源于http://www.cnblogs.com/xudong-bupt/p/3667729.html,做了少许修改)
解答:一个常见的解法是这样的,声明两个指针,两个指针的初始值都是链表的头指针,其中一个指针每次前移两个节点,称为快指针,另一个指针每次前移一个节点,称为慢指针,然后让它们两同时出发,因为链表中存在环,它们最终会第一次相遇,假设相遇在图中Pos点,相遇之后,慢指针从Pos点出发再往前移LenA个节点,就能到达Join点,最终得到我们的答案。
我想,大家读完上面的问题和解答后都会有一个疑问,为什么快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点呢?本文就是来解决大家心中的疑惑的。
网上讲解利用快慢指针寻找有环单链表中环的起点算法的博文并不少,我看了从百度搜出来的前面的四五篇,还看了LeetCode上某人推荐的一个国外博文,但是没有一个把这点讲明白的,甚至有些都是错的,错的原因有两个:(1)没认识到LenA的长度可能比环的长度大,(2)没认识到两指针相遇时,快指针可能已经绕环好几圈了。扯了些废话,现在进入正题。证明分为两部分。
(一)
首先要证明的是,两指针相遇时,慢指针还没有走完整个链表。
(1)如果慢指针第一次达到Join点时,快指针也在Join点,慢指针自然没有走完整个链表;
(2)如果慢指针第一次达到Join点时,快指针没有在Join点,我们以最极端的情况来说,假设快指针这时就在慢指针的前面一个节点,这时,快指针追上慢指针需要走最长的距离。因为快指针的速度是慢指针的两倍,所以慢指针走一圈,快指针走两圈,当慢指针第一次在环上走完一圈回到Join点时,快指针刚好走完两圈,并且已经在慢指针的前面,所以它两在慢指针第一次回到Join点之前就已经相遇。
最终,得出结论:两指针相遇时,慢指针还没有走完整个链表。
(二)
然后,我们来证明,快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点。
假设第一次相遇点为Pos,环起点为Join,头结点到环起点的长度为LenA,环起点到第一次相遇点的长度为x,第一次相遇点到环起点的长度为y,环长为R,于是有以下结果:
(1)第一次相遇时,slow走的长度 S = LenA + x;(由证明的第一部分得到)
(2)第一次相遇时,fast走的长度 2S = LenA + n*R + x;(相遇时,快指针可能已经绕环好几圈了;至少一圈,n大于等于1,因为快指针先进入环,要追上后进入的慢指针,必须得回到环起点在起点之后才能追上)
(3)LenA + x = n*R; LenA = n*R -x;
其中,(3)是由(1)(2)推导出来的。
我们的目标是根据上面三点得出慢指针在走了S + LenA后刚好到达Join点(这是清晰说明这个问题的关键)。我们尝试根据上面三点推导出我们想要的结论:
S + LenA = S + n*R - x = S + (n - 1)*R + (R - x) = S + (n - 1)*R + y
这个表达式证明了我们的结论:慢指针在移动S + (n - 1)*R个节点后刚好在快慢指针第一次相遇的位置,再移动y个节点后就刚好达到Join点。
这里顺便给出算法的具体实现方法,以方便同学们阅读。实现的具体流程是这样的:声明两个指针,两个指针的初始值都是链表的头指针,其中一个指针每次前移两个节点,称为快指针,另一个指针每次前移一个节点,称为慢指针,然后让它们两同时出发,因为链表中存在环,它们最终会第一次相遇,然后把其中一个指针移回到链表头的位置,也就是设置为链表头指针,然后让两个指针一个从相遇点出发,一个从链表头节点出发,两个都一次走一步,一直走到两个指针第一次相遇为止,这时两个指针都走了LenA的长度。下面是Java实现:
参考文章链接:
http://www.cnblogs.com/xudong-bupt/p/3667729.html
http://www.cnblogs.com/ccdev/archive/2012/09/06/2673618.html
http://blog.chinaunix.net/uid-26448049-id-3046656.html
http://learningarsenal.info/index.php/2015/08/24/detecting-start-of-a-loop-in-singly-linked-list/
(本图来源于http://www.cnblogs.com/xudong-bupt/p/3667729.html,做了少许修改)
解答:一个常见的解法是这样的,声明两个指针,两个指针的初始值都是链表的头指针,其中一个指针每次前移两个节点,称为快指针,另一个指针每次前移一个节点,称为慢指针,然后让它们两同时出发,因为链表中存在环,它们最终会第一次相遇,假设相遇在图中Pos点,相遇之后,慢指针从Pos点出发再往前移LenA个节点,就能到达Join点,最终得到我们的答案。
我想,大家读完上面的问题和解答后都会有一个疑问,为什么快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点呢?本文就是来解决大家心中的疑惑的。
网上讲解利用快慢指针寻找有环单链表中环的起点算法的博文并不少,我看了从百度搜出来的前面的四五篇,还看了LeetCode上某人推荐的一个国外博文,但是没有一个把这点讲明白的,甚至有些都是错的,错的原因有两个:(1)没认识到LenA的长度可能比环的长度大,(2)没认识到两指针相遇时,快指针可能已经绕环好几圈了。扯了些废话,现在进入正题。证明分为两部分。
(一)
首先要证明的是,两指针相遇时,慢指针还没有走完整个链表。
(1)如果慢指针第一次达到Join点时,快指针也在Join点,慢指针自然没有走完整个链表;
(2)如果慢指针第一次达到Join点时,快指针没有在Join点,我们以最极端的情况来说,假设快指针这时就在慢指针的前面一个节点,这时,快指针追上慢指针需要走最长的距离。因为快指针的速度是慢指针的两倍,所以慢指针走一圈,快指针走两圈,当慢指针第一次在环上走完一圈回到Join点时,快指针刚好走完两圈,并且已经在慢指针的前面,所以它两在慢指针第一次回到Join点之前就已经相遇。
最终,得出结论:两指针相遇时,慢指针还没有走完整个链表。
(二)
然后,我们来证明,快慢指针相遇后,慢指针再往前移LenA个节点就刚好到达Join点。
假设第一次相遇点为Pos,环起点为Join,头结点到环起点的长度为LenA,环起点到第一次相遇点的长度为x,第一次相遇点到环起点的长度为y,环长为R,于是有以下结果:
(1)第一次相遇时,slow走的长度 S = LenA + x;(由证明的第一部分得到)
(2)第一次相遇时,fast走的长度 2S = LenA + n*R + x;(相遇时,快指针可能已经绕环好几圈了;至少一圈,n大于等于1,因为快指针先进入环,要追上后进入的慢指针,必须得回到环起点在起点之后才能追上)
(3)LenA + x = n*R; LenA = n*R -x;
其中,(3)是由(1)(2)推导出来的。
我们的目标是根据上面三点得出慢指针在走了S + LenA后刚好到达Join点(这是清晰说明这个问题的关键)。我们尝试根据上面三点推导出我们想要的结论:
S + LenA = S + n*R - x = S + (n - 1)*R + (R - x) = S + (n - 1)*R + y
这个表达式证明了我们的结论:慢指针在移动S + (n - 1)*R个节点后刚好在快慢指针第一次相遇的位置,再移动y个节点后就刚好达到Join点。
这里顺便给出算法的具体实现方法,以方便同学们阅读。实现的具体流程是这样的:声明两个指针,两个指针的初始值都是链表的头指针,其中一个指针每次前移两个节点,称为快指针,另一个指针每次前移一个节点,称为慢指针,然后让它们两同时出发,因为链表中存在环,它们最终会第一次相遇,然后把其中一个指针移回到链表头的位置,也就是设置为链表头指针,然后让两个指针一个从相遇点出发,一个从链表头节点出发,两个都一次走一步,一直走到两个指针第一次相遇为止,这时两个指针都走了LenA的长度。下面是Java实现:
[code]public static ListNode findLoopStart(ListNode nodeHead) { ListNode slowPointer, fastPointer; slowPointer = nodeHead; fastPointer = nodeHead; // 寻找第一次相遇点 while (fastPointer != null) { fastPointer = fastPointer.next.next; slowPointer = slowPointer.next; if (fastPointer == slowPointer) { break; } } // 把慢指针移动到链表的开头 slowPointer = nodeHead; while (slowPointer != fastPointer) { slowPointer = slowPointer.next; fastPointer = fastPointer.next; } return slowPointer; }
[code]public class ListNode { public int val; public ListNode next; public ListNode(int x) { val = x; } @Override public String toString() { return "ListNode [val=" + val + ", next=" + next + "]"; } }
参考文章链接:
http://www.cnblogs.com/xudong-bupt/p/3667729.html
http://www.cnblogs.com/ccdev/archive/2012/09/06/2673618.html
http://blog.chinaunix.net/uid-26448049-id-3046656.html
http://learningarsenal.info/index.php/2015/08/24/detecting-start-of-a-loop-in-singly-linked-list/
相关文章推荐
- 广播机制
- MVC模型及架构
- ajax传回的json对象要用javascript中的eval()函数处理的原因
- position属性值static、relative、absolute、fixed作用
- 高效ARM C编程(中)
- github使用过的框架
- 网易公开课哈佛大学CS50学习笔记
- 百钱买百鸡问题
- gcc头文件或库的搜索路径的设定(ubuntu)
- iOS类似Android上toast效果
- java运算符优先级
- 这玩意咋玩
- 二分图的判定hihocoder1121 and hdu3478
- 大学佚事(一)
- 采用gsoap方式的webservice对接,soap绑定与http绑定的差异
- 《代码阅读方法与实践》--读书笔记
- c++实现gray code(格雷码)
- javascript中var的具体用法及含义
- 【LEETCODE】300-Longest Increasing Subsequence [Python]
- iOS UI学习笔记(六)UIViewController