先回顾一下链表的类似问题
leetcode 141 判定链表是否有环
慢指针slowPtr每次后移1个结点。快指针fastPtr每次后移2个结点
function isLinkedListContainsLoop( head){
if(head==null){
return false;
}
let slowPtr=head;
let fastPtr=head;
while(slowPtr.next!=null && fastPtr.next.next!=null){
slowPtr=slowPtr.next;
fastPtr=fastPtr.next.next;
if(slowPtr==fastPtr){
return true;
}
}
return false;
}
LeetCode 142 找出环的入口点(起点)
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt回到链表的头部,然后slowPtr和fastPtr各自从自己的位置(fastPtr从两个指针相遇的位置position出发)沿着链表出发,每次步长1,那么当fastPtr和slowPtr再次相遇的时候,就是环路的入口了。
function findLinkedListLoopBegin(head) {
if (head == null) {
return null;
}
let slowPtr = head;
let fastPtr = head;
let isLinkedListContainsLoop = false;
while (slowPtr.next != null && fastPtr.next.next != null) {
slowPtr = slowPtr.next;
fastPtr = fastPtr.next.next;
if (slowPtr == fastPtr) {
isLinkedListContainsLoop = true;
break;
}
}
if (isLinkedListContainsLoop) {
slowPtr = head;
let count = 1
while (slowPtr == fastPtr) {
slowPtr = slowPtr.next;
fastPtr = fastPtr.next;
count++
}
return slowPtr;
}
return null;
}
设环长为n,非环形部分长度为m,当第一次相遇时显然slow指针行走了 m+An+k(A表示slow行走了A圈。附:An 是因为如果环够大,则他们的相遇需要经过好几环才相遇)。fast行走了 m+B*n+k。
上面我们说了slow每次行走一步,fast每次行走两步,则在同一时间,fast行走的路程是slow的两倍。假设slow行走的路程为S,则fast行走的路程为2S。
用fast减去slow可得:
S=(B-A)*n
很显然这意味着当slow和fast相遇时他们走过的路程都为圈长的倍数。
接下来,将slow移动到起点位置,如下图:
然后每次两个指针都只移动一步,当slow移动了m,即到达了环的起点位置,此时fast总共移动了 2S+m。 考虑到S为环长的倍数,可以理解为:fast先从链表起点出发,经过了m到达环的起点,然后绕着环移动了几圈,最终又到达环的起点,值为2S+m。所以fast最终必定处在环的起点位置。即两者相遇点即为环的起点位置。
衍生问题2,求环的大小(长度)
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt不动,fast 绕着环移动,每次移动一步,计数count加1,当两指针再次相遇时,count即是环的大小
回归原题,也是用快慢节点
function circularArrayLoop(nums) {
let n = nums.length;
if (n <= 1) {
return false
}
function getNext(i) {
return (i + nums[i] + n) % n
}
for (let i = 0; i < n; i++) {
let slow = i, fast = getNext(i);
//确保总是朝着一个方向前进
while (nums[slow] * nums[i] > 0 && nums[fast] * nums[i] > 0) {
if (slow == fast) {
//判断是否只有一个元素
if (slow == getNext(slow)) {
break;
}
return true;
}
slow = getNext(slow);
fast = getNext(fast);//快指针每次走两步
if (nums[fast] * nums[i] < 0) {//如果方向反了
break;
}
fast = getNext(fast);
}
}
return false;
}
console.log(circularArrayLoop([2, -1, 1, 2, 2]))
console.log(circularArrayLoop([-1, 2]))
console.log(circularArrayLoop([-2, 1, -1, -2, -2]))