🧠
Fast & Slow Pointer Cycle Detection
💡 This approach uses two pointers moving at different speeds to detect cycles efficiently, inspired by Floyd's cycle detection in linked lists. It avoids extra space and reduces time complexity.
Intuition
Use a slow pointer moving one step and a fast pointer moving two steps. If they meet, a cycle exists. Check direction consistency and cycle length > 1.
Algorithm
- Iterate over each index as a potential cycle start.
- If the element is not visited, use fast and slow pointers to detect a cycle.
- Move pointers while direction is consistent and cycle length > 1.
- If pointers meet, return true; else mark all visited indices in this path to avoid reprocessing.
💡 The challenge is to handle direction consistency and avoid infinite loops by marking visited elements.
def circularArrayLoop(nums):
n = len(nums)
def next_index(i):
return (i + nums[i]) % n
for i in range(n):
if nums[i] == 0:
continue
direction = nums[i] > 0
slow, fast = i, i
while True:
slow = next_index(slow)
fast = next_index(next_index(fast))
if (nums[slow] > 0) != direction or (nums[fast] > 0) != direction:
break
if slow == fast:
if slow == next_index(slow):
break
return True
slow = i
val = nums[i]
while (nums[slow] > 0) == direction:
next_i = next_index(slow)
nums[slow] = 0
slow = next_i
return False
# Driver code
if __name__ == '__main__':
print(circularArrayLoop([2, -1, 1, 2, 2])) # True
print(circularArrayLoop([-1, 2])) # False
Line Notes
def next_index(i):Helper to compute next index with wrap-around.
if nums[i] == 0:Skip already visited or processed elements.
direction = nums[i] > 0Determine movement direction for consistency.
slow, fast = i, iInitialize two pointers at the start index.
slow = next_index(slow)Move slow pointer one step.
fast = next_index(next_index(fast))Move fast pointer two steps.
if (nums[slow] > 0) != direction or (nums[fast] > 0) != direction:Break if direction changes.
if slow == fast:Cycle detected if pointers meet.
if slow == next_index(slow):Ignore single-element loops.
nums[slow] = 0Mark visited elements to avoid reprocessing.
public class Solution {
public boolean circularArrayLoop(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
if (nums[i] == 0) continue;
boolean direction = nums[i] > 0;
int slow = i, fast = i;
while (true) {
slow = nextIndex(nums, n, slow);
fast = nextIndex(nums, n, nextIndex(nums, n, fast));
if ((nums[slow] > 0) != direction || (nums[fast] > 0) != direction) break;
if (slow == fast) {
if (slow == nextIndex(nums, n, slow)) break;
return true;
}
}
slow = i;
int val = nums[i];
while ((nums[slow] > 0) == direction) {
int next = nextIndex(nums, n, slow);
nums[slow] = 0;
slow = next;
}
}
return false;
}
private int nextIndex(int[] nums, int n, int i) {
int next = (i + nums[i]) % n;
if (next < 0) next += n;
return next;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.circularArrayLoop(new int[]{2, -1, 1, 2, 2})); // true
System.out.println(sol.circularArrayLoop(new int[]{-1, 2})); // false
}
}
Line Notes
if (nums[i] == 0) continue;Skip elements already processed to avoid redundant work.
boolean direction = nums[i] > 0;Determine movement direction for consistency.
int slow = i, fast = i;Initialize two pointers at the start index.
slow = nextIndex(nums, n, slow);Move slow pointer one step.
fast = nextIndex(nums, n, nextIndex(nums, n, fast));Move fast pointer two steps.
if ((nums[slow] > 0) != direction || (nums[fast] > 0) != direction) break;Break if direction changes.
if (slow == fast) {Cycle detected if pointers meet.
nums[slow] = 0;Mark visited elements to avoid reprocessing.
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int nextIndex(vector<int>& nums, int n, int i) {
int next = (i + nums[i]) % n;
if (next < 0) next += n;
return next;
}
bool circularArrayLoop(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; i++) {
if (nums[i] == 0) continue;
bool direction = nums[i] > 0;
int slow = i, fast = i;
while (true) {
slow = nextIndex(nums, n, slow);
fast = nextIndex(nums, n, nextIndex(nums, n, fast));
if ((nums[slow] > 0) != direction || (nums[fast] > 0) != direction) break;
if (slow == fast) {
if (slow == nextIndex(nums, n, slow)) break;
return true;
}
}
slow = i;
while ((nums[slow] > 0) == direction) {
int next = nextIndex(nums, n, slow);
nums[slow] = 0;
slow = next;
}
}
return false;
}
};
int main() {
Solution sol;
vector<int> nums1 = {2, -1, 1, 2, 2};
cout << boolalpha << sol.circularArrayLoop(nums1) << endl; // true
vector<int> nums2 = {-1, 2};
cout << boolalpha << sol.circularArrayLoop(nums2) << endl; // false
return 0;
}
Line Notes
if (nums[i] == 0) continue;Skip elements already processed to avoid redundant work.
bool direction = nums[i] > 0;Determine movement direction for consistency.
int slow = i, fast = i;Initialize two pointers at the start index.
slow = nextIndex(nums, n, slow);Move slow pointer one step.
fast = nextIndex(nums, n, nextIndex(nums, n, fast));Move fast pointer two steps.
if ((nums[slow] > 0) != direction || (nums[fast] > 0) != direction) break;Break if direction changes.
if (slow == fast) {Cycle detected if pointers meet.
nums[slow] = 0;Mark visited elements to avoid reprocessing.
var circularArrayLoop = function(nums) {
const n = nums.length;
const nextIndex = (i) => {
let next = (i + nums[i]) % n;
if (next < 0) next += n;
return next;
};
for (let i = 0; i < n; i++) {
if (nums[i] === 0) continue;
const direction = nums[i] > 0;
let slow = i, fast = i;
while (true) {
slow = nextIndex(slow);
fast = nextIndex(nextIndex(fast));
if ((nums[slow] > 0) !== direction || (nums[fast] > 0) !== direction) break;
if (slow === fast) {
if (slow === nextIndex(slow)) break;
return true;
}
}
slow = i;
while ((nums[slow] > 0) === direction) {
let next = nextIndex(slow);
nums[slow] = 0;
slow = next;
}
}
return false;
};
// Test cases
console.log(circularArrayLoop([2, -1, 1, 2, 2])); // true
console.log(circularArrayLoop([-1, 2])); // false
Line Notes
if (nums[i] === 0) continue;Skip elements already processed to avoid redundant work.
const direction = nums[i] > 0;Determine movement direction for consistency.
let slow = i, fast = i;Initialize two pointers at the start index.
slow = nextIndex(slow);Move slow pointer one step.
fast = nextIndex(nextIndex(fast));Move fast pointer two steps.
if ((nums[slow] > 0) !== direction || (nums[fast] > 0) !== direction) break;Break if direction changes.
if (slow === fast) {Cycle detected if pointers meet.
nums[slow] = 0;Mark visited elements to avoid reprocessing.
Each element is visited at most once due to marking visited elements as 0, so total time is linear. Space is constant as no extra data structures are used.
💡 For n=100000, this approach can handle all elements efficiently within time limits.
Interview Verdict: Accepted
This is the optimal approach commonly expected in interviews for cycle detection in arrays.