简单暴力解法
/** 标记法 **/// 给每个已遍历过的节点加标志位,遍历链表,当出现下一个节点已被标志时,则证明单链表有环// 时间复杂度:O(n) 空间复杂度:O(n)var hasCycle = function(head) {while(head) {if(head.flag) return truehead.flag = truehead = head.next}return false};/** JSON.stringify **/// 利用 JSON.stringify() 不能序列化含有循环引用的结构// 时间复杂度:O(n) 空间复杂度:O(n)var hasCycle = function(head) {try{JSON.stringify(head);return false;}catch(err){return true;}};
最优解
首先创建两个指针 p1 和 p2(在 Java 里就是两个对象引用),让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针 p1 每次向后移动 1 个节点,让指针 p2 每次向后移动 2 个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。
学过小学奥数的读者,一定听说过数学上的追及问题。此方法就类似于一个追及问题。
在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。
假设链表的节点数量为 n,则该算法的时间复杂度为 O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是 O(1)。
/** 快慢双指针 **/var hasCycle = function(head) {let p1 = head;let p2 = head;while (p2 != null && p2.next != null){p1 = p1.next;p2 = p2.next.next;if(p1 == p2){return true;}}return false;};
如果有环,求环长度
var cycleLength = function(head) {let p1 = head;let p2 = head;let meetCount = 0;while (p2 != null && p2.next != null){p1 = p1.next;p2 = p2.next.next;if(p1 == p2){let length = 0;while (p2 != null && p2.next != null){p1 = p1.next;p2 = p2.next.next;length++;if(p1 == p2){return length;}}}}return false;};
