leetcode中刷题的时候遇到环形链表的问题,然后网上查找一下,对环形链表的问题做下记录。

首先看一张环形链表的图,如图所示:

                            



1.判断一个链表是否有环

  a.采用hashset方式,遍历链表,每次将链表节点存放到set中,如果如果遍历过程中中发现了set对应的值,则认为链表存在环,代码如下:

 1 var hasCycle = function(head) {
 2     const set = new Set(); // 通过创建一个set, 向set中添加listnode,如果每次循环的时候已经发现set中存在listnode,则认为有环
 3     while (head !== null) {
 4         if (set.has(head)) {
 5             return true;
 6         } else {
 7             set.add(head);
 8         }
 9         head = head.next;
10     }
11     return false
12 }

  此方式由于创建了set,消耗了多余的空间。

 

  b.采用快慢指针方式,设置一个fast指针,每次移动两步,一个slow指针,每次移动一步,一直循环,如果出现fast == slow重合情况,则认为链表有环,代码如下:

 1 var hasCycle = function(head) {
 2     if (!head || !head.next) {
 3         return null;
 4     }
 5     
 6     let fast = head;
 7     let slow = head;
 8     while (fast.next && fast.next.next) {
 9         slow = slow.next;
10         fast = fast.next.next;
11         if (slow == fast) { // 存在相遇的情况,则链表存在环
12             return slow;
13         }
14     }
15     return null;
16 }

2.计算链表环的长度:

  在环上相遇后,记录第一次相遇点为B,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。

  设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2*len步,相遇时多走了一圈:

  环长=2*len-len。 

  执行第一步判断是否存在环,得到第一次相遇节点位置,则fast与slow再次相遇的时候,即得到环长

 1 const meetingNode = hasCycle(head);
 2 var getLength = function (meetingNode){
 3     let length = 0;
 4     let fast = meetingNode;
 5     let slow = meetingNode;
 6     while (true) {
 7         fast = fast.next.next;
 8         slow = slow.next;
 9         length += 1;
10         if (fast === slow) {
11            break;
12         }
13     }
14     return length;
15 } 

3.查找链表环的起点:

  假设起点Head到环起点C的长度为LenA;开始到第一次相遇,移动了t步;环的长度为R;环的起点到二者第一次相遇位置的距离为L1,二者相遇的时候快指针已经在环上转了n圈。

  则有如下等式成立: 慢指针(slow)移动的距离=L3 + L1; 即 t = L3 + L1;

            快指针移动的距离=L3 + n * R + L1; 即 2t = L3 + n * R + L1;

            所以 2*(L3 + L1) = L3 + n*R + L1;

            即 L3 = n * R - L1;

  上面的等式可以这样理解,如果慢指针从链表起点开始移动,快指针从环起点开始移动,当慢指针从起点走到环起点的时候,快指针一直在环上移动,而且还差x步就移动了n圈。进而可以得到结论,如果慢指针从链表起点开始移动,快指针从二者第一次相遇点开始移动,当慢指针到达环起点的时候,快指针也正好达到环起点,即二者指向相同的节点。

 1 var detectCycle = function(head) {
 2     if (!head || !head.next) {
 3        return null;
 4    }
 5     
 6     let fast = head;
 7     let slow = head;
 8     let pos = head; 
 9     
10     while (fast.next && fast.next.next) {
11         slow = slow.next;
12         fast = fast.next.next;
13         
14         if (slow == fast) {
15             // 头结点至环开始的距离与快慢指针相遇点至环开始的距离是一致的;
16             // 当快慢指针相遇后,头结点与慢指针继续向前移到,头结点指针与慢指针会在环开始位置相遇
17             while (slow != pos) {
18                 slow = slow.next;
19                 pos = pos.next;
20             }
21             return pos; // 此时的pos即为环的开始节点
22         }
23     }
24     
25     return null;
26 };

 

如有错误,🙏谢谢指正。