環形數組循環
給定一個含有正整數和負整數的環形數組nums,如果某個索引中的數k為正數,則向前移動 k個索引,相反如果是負數-k,則向后移動k個索引。因為數組是環形的,所以可以假設最后一個元素的下一個元素是第一個元素,而第一個元素的前一個元素是最后一個元素,確定nums中是否存在循環或周期。循環必須在相同的索引處開始和結束並且循環長度>1。此外,一個循環中的所有運動都必須沿着同一方向進行,換句話說,一個循環中不能同時包括向前的運動和向后的運動。
示例
輸入:[2,-1,1,2,2]
輸出:true
解釋:存在循環,按索引 0 -> 2 -> 3 -> 0 。循環長度為 3 。
輸入:[-1,2]
輸出:false
解釋:按索引 1 -> 1 -> 1 ... 的運動無法構成循環,因為循環的長度為 1 。根據定義,循環的長度必須大於 1 。
輸入:[-2,1,-1,-2,-2]
輸出:false
解釋:按索引 1 -> 2 -> 1 -> ... 的運動無法構成循環,因為按索引 1 -> 2 的運動是向前的運動,而按索引 2 -> 1 的運動是向后的運動。一個循環中的所有運動都必須沿着同一方向進行。
題解
/**
* @param {number[]} nums
* @return {boolean}
*/
var circularArrayLoop = function(nums) {
var n = nums.length;
var getNext = x => {
var nextIndex = (x+nums[x])%n;
return nextIndex >= 0 ? nextIndex : nextIndex+n;
};
for(let i=0;i<n;++i) {
if(nums[i] === 0) continue;
let slow = i;
let fast = getNext(i);
while(nums[slow]*nums[fast] > 0 && nums[fast] * nums[getNext(fast)] > 0){
if(slow === fast){
if(slow === getNext(slow)) break;
else return true;
}
slow = getNext(slow);
fast = getNext(getNext(fast));
}
let tmp = i;
let val = nums[tmp];
while(val * nums[tmp] > 0){
let k = getNext(tmp);
nums[tmp] = 0;
tmp = k;
}
}
return false;
};
思路
首先需要解釋一下題意,以示例1中[2,-1,1,2,2]為例,最開始是索引0值為2,那么索引向前走2步到索引2值為1,繼續向前走1步到達索引3值為2,再向前走2步循環索引回到0,所以這完成了一次循環,這里的起始點並不一定是索引0,起始點可以為任意索引位置,其次就是限制條件循環的長度必須大於1以及一個循環中的所有運動都必須沿着同一方向進行。
本題使用快慢指針來做,快指針每次走兩步,慢指針每次走一步,如果能夠達成循環那么快慢指針必定會相遇,當然在此處一步與兩步指的是移動一個nums[i]的步長,不是移動index+1,首先定義一個n為數組長度以及getNext方法作為取得該點的下一步的索引值,之后遍歷數組,根據定義,數組中不能存在0元素,所以以0為標記值進行剪枝,以慢指針指向i,快指針指向下一步的索引,while循環中第一個判斷是保證慢指針與快指針指向的數組值符號相同,第二個判斷是保證快指針指向的數組值與下一個快指針指向的數組值同號,保證一個循環中的所有運動都必須沿着同一方向進行,之后如果快慢指針相遇,則判斷是否循環的長度為1,若循環的長度為1則不符合條件,便繼續查找,否則就可以說明該數組中存在循環,之后便是slow指針走一步,fast指針走兩部,最后需要剪枝,因為已經遍歷過的元素不可能出現在循環當中,所以將以i為索引開始的每一步都置0,用以實現剪枝。
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://leetcode-cn.com/problems/circular-array-loop
