请解释这段单链表环检测代码的实现原理
详解单链表环检测的O(n)时间复杂度算法
这是一个设计非常巧妙的单链表环检测算法,咱们一步步拆解它的工作逻辑,帮你彻底搞明白:
先看完整代码
function boolean hasLoop(Node startNode){ Node currentNode = startNode; Node checkNode = null; int since = 0; int sinceScale = 2; do { if (checkNode == currentNode) return true; if (since >= sinceScale){ checkNode = currentNode; since = 0; sinceScale = 2*sinceScale; } since++; } while (currentNode = currentNode.next()); return false; }
核心设计思路
这个算法没有用常见的快慢指针(Floyd判圈法),而是采用阶段性设置检查点+逐步比对的策略:每隔一段指数级增长的步数,就把当前节点标记为「检查点」,之后每走一步都和这个检查点对比——如果某一步当前节点和检查点重合,说明链表存在环;如果走到链表末尾(currentNode.next()为null),则说明无环。
变量与代码逐行解析
咱们逐个看关键变量和循环里的逻辑:
currentNode:遍历链表的主指针,每次循环都会向后走一步checkNode:用来保存「检查点」节点,后续每一步都会和它比对since:记录从上次设置检查点以来,已经走了多少步sinceScale:下一次设置检查点的步长阈值,初始为2,每次设置检查点后都会翻倍(2→4→8→16...)
循环内的执行流程:
- 环检测判断:先检查
checkNode是否和currentNode相等——如果相等,说明当前指针绕回了之前标记的检查点,链表肯定存在环,直接返回true。 - 更新检查点:如果从上次设检查点到现在走的步数
since达到了阈值sinceScale,就把当前节点设为新的检查点,重置since为0,同时把阈值翻倍(指数级扩大步长)。 - 步数计数:每完成一次循环,
since加1,记录距离上次检查点的步数。 - 循环终止条件:
currentNode = currentNode.next()是赋值操作,只要当前节点的下一个节点不为null,就继续遍历;如果走到链表末尾(next()为null),循环结束,返回false说明无环。
时间复杂度为什么是O(n)
关于时间复杂度的证明,原文给出了说明:
该解决方案的时间复杂度为O(n),因为sinceScale随next()调用次数线性增长。一旦sinceScale大于环的大小,最多再经过n次next()调用即可检测到环。
咱们再通俗解释下:
因为sinceScale是指数级增长的,所以设置检查点的次数非常少(只有log(n)级别)。每次设置检查点后,后续的遍历最多走sinceScale步就会遇到检查点(如果有环的话)。把所有阶段的遍历步数加起来,总次数不会超过2n,所以整体时间复杂度是线性的O(n)。
内容的提问来源于stack exchange,提问作者Vaishali Gupta
相关产品推荐
相关产品推荐

