🧠
Fast and Slow Pointer (Floyd's Cycle Detection)
💡 This approach uses two pointers moving at different speeds to detect cycles efficiently without extra space, a classic technique for cycle detection in linked structures.
Intuition
Use a slow pointer moving one step and a fast pointer moving two steps. If they meet, a cycle exists. Check direction and cycle length constraints.
Algorithm
- Iterate over each index as a potential start.
- If the element is zero, skip since it was visited or invalid.
- Initialize slow and fast pointers at start.
- Move slow by one step and fast by two steps, checking direction consistency.
- If slow meets fast, check cycle length > 1 and return true.
- Mark all visited elements in the current path as zero to avoid reprocessing.
- If no cycle found, return false.
💡 The marking step avoids revisiting processed paths, improving efficiency.
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
slow, fast = i, i
direction = nums[i] > 0
while True:
slow_next = next_index(slow)
fast_next = next_index(fast)
if (nums[slow_next] > 0) != direction or (nums[fast_next] > 0) != direction:
break
fast_next = next_index(fast_next)
if (nums[fast_next] > 0) != direction:
break
slow, fast = slow_next, fast_next
if slow == fast:
if slow == next_index(slow):
break
return True
# Mark all nodes in the current path as 0
marker = i
while (nums[marker] != 0) and ((nums[marker] > 0) == direction):
next_marker = next_index(marker)
nums[marker] = 0
marker = next_marker
return False
# Example usage
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 indices already processed or invalid.
direction = nums[i] > 0Determine jump direction for cycle consistency.
slow_next = next_index(slow)Move slow pointer one step.
fast_next = next_index(fast)Move fast pointer one step first.
fast_next = next_index(fast_next)Move fast pointer second step.
if slow == fast:Pointers meet, possible cycle detected.
if slow == next_index(slow):Check for self-loop, invalid cycle.
nums[marker] = 0Mark visited indices 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;
int slow = i, fast = i;
boolean direction = nums[i] > 0;
while (true) {
int slowNext = nextIndex(nums, slow);
int fastNext = nextIndex(nums, fast);
if ((nums[slowNext] > 0) != direction || (nums[fastNext] > 0) != direction) break;
fastNext = nextIndex(nums, fastNext);
if ((nums[fastNext] > 0) != direction) break;
slow = slowNext;
fast = fastNext;
if (slow == fast) {
if (slow == nextIndex(nums, slow)) break;
return true;
}
}
int marker = i;
while (nums[marker] != 0 && (nums[marker] > 0) == direction) {
int next = nextIndex(nums, marker);
nums[marker] = 0;
marker = next;
}
}
return false;
}
private int nextIndex(int[] nums, int i) {
int n = nums.length;
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 indices already processed or invalid.
boolean direction = nums[i] > 0;Determine jump direction for cycle consistency.
int slowNext = nextIndex(nums, slow);Move slow pointer one step.
int fastNext = nextIndex(nums, fast);Move fast pointer one step first.
fastNext = nextIndex(nums, fastNext);Move fast pointer second step.
if (slow == fast) {Pointers meet, possible cycle detected.
if (slow == nextIndex(nums, slow)) break;Check for self-loop, invalid cycle.
nums[marker] = 0;Mark visited indices to avoid reprocessing.
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int nextIndex(const vector<int>& nums, int i) {
int n = nums.size();
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;
int slow = i, fast = i;
bool direction = nums[i] > 0;
while (true) {
int slowNext = nextIndex(nums, slow);
int fastNext = nextIndex(nums, fast);
if ((nums[slowNext] > 0) != direction || (nums[fastNext] > 0) != direction) break;
fastNext = nextIndex(nums, fastNext);
if ((nums[fastNext] > 0) != direction) break;
slow = slowNext;
fast = fastNext;
if (slow == fast) {
if (slow == nextIndex(nums, slow)) break;
return true;
}
}
int marker = i;
while (nums[marker] != 0 && (nums[marker] > 0) == direction) {
int next = nextIndex(nums, marker);
nums[marker] = 0;
marker = next;
}
}
return false;
}
};
int main() {
Solution sol;
vector<int> nums1 = {2, -1, 1, 2, 2};
vector<int> nums2 = {-1, 2};
cout << boolalpha << sol.circularArrayLoop(nums1) << endl; // true
cout << boolalpha << sol.circularArrayLoop(nums2) << endl; // false
return 0;
}
Line Notes
if (nums[i] == 0) continue;Skip indices already processed or invalid.
bool direction = nums[i] > 0;Determine jump direction for cycle consistency.
int slowNext = nextIndex(nums, slow);Move slow pointer one step.
int fastNext = nextIndex(nums, fast);Move fast pointer one step first.
fastNext = nextIndex(nums, fastNext);Move fast pointer second step.
if (slow == fast) {Pointers meet, possible cycle detected.
if (slow == nextIndex(nums, slow)) break;Check for self-loop, invalid cycle.
nums[marker] = 0;Mark visited indices to avoid reprocessing.
function circularArrayLoop(nums) {
const n = nums.length;
function 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;
let slow = i, fast = i;
const direction = nums[i] > 0;
while (true) {
let slowNext = nextIndex(slow);
let fastNext = nextIndex(fast);
if ((nums[slowNext] > 0) !== direction || (nums[fastNext] > 0) !== direction) break;
fastNext = nextIndex(fastNext);
if ((nums[fastNext] > 0) !== direction) break;
slow = slowNext;
fast = fastNext;
if (slow === fast) {
if (slow === nextIndex(slow)) break;
return true;
}
}
let marker = i;
while (nums[marker] !== 0 && (nums[marker] > 0) === direction) {
let next = nextIndex(marker);
nums[marker] = 0;
marker = next;
}
}
return false;
}
// Example usage
console.log(circularArrayLoop([2, -1, 1, 2, 2])); // true
console.log(circularArrayLoop([-1, 2])); // false
Line Notes
if (nums[i] === 0) continue;Skip indices already processed or invalid.
const direction = nums[i] > 0;Determine jump direction for cycle consistency.
let slowNext = nextIndex(slow);Move slow pointer one step.
let fastNext = nextIndex(fast);Move fast pointer one step first.
fastNext = nextIndex(fastNext);Move fast pointer second step.
if (slow === fast) {Pointers meet, possible cycle detected.
if (slow === nextIndex(slow)) break;Check for self-loop, invalid cycle.
nums[marker] = 0;Mark visited indices to avoid reprocessing.
Each element is visited at most once due to marking, so total time is linear. Space is constant as no extra data structures are used except pointers.
💡 For n=100000, this means about 100000 operations, efficient for large inputs.
Interview Verdict: Accepted
This is the optimal approach for cycle detection in this problem, balancing speed and space.