赞
踩
给定一个链表,如何去判断是否该链表中有环,示意图如下。
图1:环形链表
图2:普通单链表
解决该问题比较常用的方法就是快慢指针法,也就是所谓的龟兔赛跑算法的简化版:基本原理就是定义两个链表指针分别指向链表的头结点和第二个结点(注:在力扣中一般链表的头结点就是首元结点),在两个指针都进入链表环以后,总会相遇在同一个结点,反之如果是普通链表则指针最终会指向空。参考程序C++如下:
- /**
- * struct ListNode { //链表结点的定义
- * int val;
- * ListNode *next;
- * ListNode(int x) : val(x), next(NULL) {}
- * };
- */
- class Solution {
- public:
- bool hasCycle(ListNode *head) {
- if(head == NULL){ //判断边界条件
- return false;
- }
- ListNode* slow = head;
- ListNode* fast = head->next;
- while(fast != NULL && fast->next != NULL){ //当快指正不为空时执行循环
- if(slow == fast){ //快指正与慢指针套圈时有环
- return true;
- }
- slow = slow->next;
- fast = fast->next->next;
- }
- return false;
- }
- };
相信有部分小伙伴会对这一结论感到奇怪:为什么两个指针速度不一样,在进入环后一定就会相遇在同一结点呢?下面着重对这一个问题谈谈我个人的理解。
若一个链表环有 个结点,存在两个指针:一个是慢指针,每步移动一个结点,记为 ,一个是快指针,每步移动两个结点,记为 ,为了简化问题,假设这两个指针从同一个起点出发,且该起点的标号为1。试证明两指针必然会相遇在同一结点,并求出此时走过的步数 应该满足的关系。
该问题为证明性问题,我们可以分别求出经 步后 与 指针所指向结点的标号和,然后对标号相等的条件进行求解,若结果满足题意限制即上述结论得证。示意图绘制如下:
图3:经过 步后各个指针的示意图
针对 指针,其速度为一步一格,初始值为1,标号建立如式3-1:
针对指针,其速度为一步两格,初始值为1,标号建立如式3-2:
其中,表示取模运算,可能有的小伙伴凭着直觉会认为要对结点个数 分奇偶讨论,因为指针每次移动两格,但事实上,奇偶讨论的结果和式(3-2)一样,有兴趣的可以自己去验证。
若要使得两个指针相遇,即达到同一结点,满足其对应标号相等:
结合取模运算的性质进行整理计算有:
即当步数 为总结点数 的整数倍时两指针标号相等:
又因为对于一个链表,结点数 为一个有限值,总是能够找到这样的正整数 ,使得式3-5成立,故而能够使得两指针的标号相等,即两个指针必然会相遇。
此时根据式3-5和式3-1求得相遇点的标号为:
即两指针总是在起始点相遇,证毕。
上面我们只是讨论了两个指针在同一起始点时的相遇问题,但事实上这还远远不够,因为两个指针不能够保证同时到达环的起点,如图4所示,当slow指针到达环内时,有:
图4:示意图
对于该问题先进行数学描述,分析从slow指针进入链表环起之后能否实现两个指针相遇。
图5:两指针初始位置和移动 步后的位置
若选择 指针的初始位置 为参考点,该结点所在的位置标号为1,可以认为 指针的初始位置 为参考点的位置的基础上多走了 步,即:
其中
针对 指针,其速度为一步一格,初始值为 ,标号建立如式4-3:
针对指针,其速度为一步两格,初始值为1,标号建立如式4-4:
当标号相同时,和上面同样的整理有:
即当步数i与j的差为结点数n的整数倍时成立。
此时根据式4-5和式4-3求得相遇点的标号为:
可见,两个指针的从同一起始点出发是当 时的特殊情况。
此处分析是我个人对龟兔赛跑算法的理解,上述证明过程可能有不严谨之处,欢迎各位小伙伴积极讨论指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。