先回顧一下鏈表的類似問題
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]))