Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Move from brute-force thinking to an efficient approach using array strategy.
You are playing a game involving a circular array of non-zero integers nums. Each nums[i] denotes the number of indices forward/backward you must move if you are located at index i:
nums[i] is positive, move nums[i] steps forward, andnums[i] is negative, move abs(nums[i]) steps backward.Since the array is circular, you may assume that moving forward from the last element puts you on the first element, and moving backwards from the first element puts you on the last element.
A cycle in the array consists of a sequence of indices seq of length k where:
seq[0] -> seq[1] -> ... -> seq[k - 1] -> seq[0] -> ...nums[seq[j]] is either all positive or all negative.k > 1Return true if there is a cycle in nums, or false otherwise.
Example 1:
Input: nums = [2,-1,1,2,2] Output: true Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward. We can see the cycle 0 --> 2 --> 3 --> 0 --> ..., and all of its nodes are white (jumping in the same direction).
Example 2:
Input: nums = [-1,-2,-3,-4,-5,6] Output: false Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward. The only cycle is of size 1, so we return false.
Example 3:
Input: nums = [1,-1,5,1,4] Output: true Explanation: The graph shows how the indices are connected. White nodes are jumping forward, while red is jumping backward. We can see the cycle 0 --> 1 --> 0 --> ..., and while it is of size > 1, it has a node jumping forward and a node jumping backward, so it is not a cycle. We can see the cycle 3 --> 4 --> 3 --> ..., and all of its nodes are white (jumping in the same direction).
Constraints:
1 <= nums.length <= 5000-1000 <= nums[i] <= 1000nums[i] != 0Follow up: Could you solve it in O(n) time complexity and O(1) extra space complexity?
Problem summary: You are playing a game involving a circular array of non-zero integers nums. Each nums[i] denotes the number of indices forward/backward you must move if you are located at index i: If nums[i] is positive, move nums[i] steps forward, and If nums[i] is negative, move abs(nums[i]) steps backward. Since the array is circular, you may assume that moving forward from the last element puts you on the first element, and moving backwards from the first element puts you on the last element. A cycle in the array consists of a sequence of indices seq of length k where: Following the movement rules above results in the repeating index sequence seq[0] -> seq[1] -> ... -> seq[k - 1] -> seq[0] -> ... Every nums[seq[j]] is either all positive or all negative. k > 1 Return true if there is a cycle in nums, or false otherwise.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Hash Map · Two Pointers
[2,-1,1,2,2]
[-1,-2,-3,-4,-5,6]
[1,-1,5,1,4]
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #457: Circular Array Loop
class Solution {
private int n;
private int[] nums;
public boolean circularArrayLoop(int[] nums) {
n = nums.length;
this.nums = nums;
for (int i = 0; i < n; ++i) {
if (nums[i] == 0) {
continue;
}
int slow = i, fast = next(i);
while (nums[slow] * nums[fast] > 0 && nums[slow] * nums[next(fast)] > 0) {
if (slow == fast) {
if (slow != next(slow)) {
return true;
}
break;
}
slow = next(slow);
fast = next(next(fast));
}
int j = i;
while (nums[j] * nums[next(j)] > 0) {
nums[j] = 0;
j = next(j);
}
}
return false;
}
private int next(int i) {
return (i + nums[i] % n + n) % n;
}
}
// Accepted solution for LeetCode #457: Circular Array Loop
func circularArrayLoop(nums []int) bool {
for i, num := range nums {
if num == 0 {
continue
}
slow, fast := i, next(nums, i)
for nums[slow]*nums[fast] > 0 && nums[slow]*nums[next(nums, fast)] > 0 {
if slow == fast {
if slow != next(nums, slow) {
return true
}
break
}
slow, fast = next(nums, slow), next(nums, next(nums, fast))
}
j := i
for nums[j]*nums[next(nums, j)] > 0 {
nums[j] = 0
j = next(nums, j)
}
}
return false
}
func next(nums []int, i int) int {
n := len(nums)
return (i + nums[i]%n + n) % n
}
# Accepted solution for LeetCode #457: Circular Array Loop
class Solution:
def circularArrayLoop(self, nums: List[int]) -> bool:
n = len(nums)
def next(i):
return (i + nums[i] % n + n) % n
for i in range(n):
if nums[i] == 0:
continue
slow, fast = i, next(i)
while nums[slow] * nums[fast] > 0 and nums[slow] * nums[next(fast)] > 0:
if slow == fast:
if slow != next(slow):
return True
break
slow, fast = next(slow), next(next(fast))
j = i
while nums[j] * nums[next(j)] > 0:
nums[j] = 0
j = next(j)
return False
// Accepted solution for LeetCode #457: Circular Array Loop
struct Solution;
impl Solution {
fn circular_array_loop(mut nums: Vec<i32>) -> bool {
let n = nums.len();
for i in 0..n {
if Self::next(&nums, i) == i {
nums[i] = 0;
}
}
for i in 0..n {
if nums[i] == 0 {
continue;
}
let mut slow = i;
let mut fast = i;
while nums[slow] * nums[Self::next(&nums, fast)] > 0
&& nums[slow] * nums[Self::next(&nums, Self::next(&nums, fast))] > 0
{
slow = Self::next(&nums, slow);
fast = Self::next(&nums, Self::next(&nums, fast));
if slow == fast {
return true;
}
}
let mut j = i;
let val = nums[i];
while nums[j] * val > 0 {
let next = Self::next(&nums, j);
nums[j] = 0;
j = next;
}
}
false
}
fn next(nums: &[i32], index: usize) -> usize {
let n = nums.len();
let index = index as i32 + nums[index];
let index = if index < 0 {
n as i32 + (index % n as i32)
} else {
index % n as i32
};
(index as usize) % n
}
}
#[test]
fn test() {
let nums = vec![2, -1, 1, 2, 2];
let res = true;
assert_eq!(Solution::circular_array_loop(nums), res);
let nums = vec![-1, 2];
let res = false;
assert_eq!(Solution::circular_array_loop(nums), res);
let nums = vec![-2, 1, -1, -2, -2];
let res = false;
assert_eq!(Solution::circular_array_loop(nums), res);
let nums = vec![-1];
let res = false;
assert_eq!(Solution::circular_array_loop(nums), res);
}
// Accepted solution for LeetCode #457: Circular Array Loop
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #457: Circular Array Loop
// class Solution {
// private int n;
// private int[] nums;
//
// public boolean circularArrayLoop(int[] nums) {
// n = nums.length;
// this.nums = nums;
// for (int i = 0; i < n; ++i) {
// if (nums[i] == 0) {
// continue;
// }
// int slow = i, fast = next(i);
// while (nums[slow] * nums[fast] > 0 && nums[slow] * nums[next(fast)] > 0) {
// if (slow == fast) {
// if (slow != next(slow)) {
// return true;
// }
// break;
// }
// slow = next(slow);
// fast = next(next(fast));
// }
// int j = i;
// while (nums[j] * nums[next(j)] > 0) {
// nums[j] = 0;
// j = next(j);
// }
// }
// return false;
// }
//
// private int next(int i) {
// return (i + nums[i] % n + n) % n;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Two nested loops check every pair of elements. The outer loop picks one element, the inner loop scans the rest. For n elements that is n × (n−1)/2 comparisons = O(n²). No extra memory — just two loop variables.
Each pointer traverses the array at most once. With two pointers moving inward (or both moving right), the total number of steps is bounded by n. Each comparison is O(1), giving O(n) overall. No auxiliary data structures are needed — just two index variables.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Zero-count keys stay in map and break distinct/count constraints.
Usually fails on: Window/map size checks are consistently off by one.
Fix: Delete keys when count reaches zero.
Wrong move: Advancing both pointers shrinks the search space too aggressively and skips candidates.
Usually fails on: A valid pair can be skipped when only one side should move.
Fix: Move exactly one pointer per decision branch based on invariant.