Lesson 125 of 164
Check for a Loop in an Array of Jumps
Given an array arr of n integers, each element describes a jump from its index, wrapping around the ends:
- a positive
arr[i]moves forward: the next index is(i + arr[i]) % n, - a negative
arr[i]moves backward by|arr[i]|, also wrapping around, - if
arr[i] % n == 0, indexihas no move.
Return true if following the jumps can form a loop that visits more than one index.
Example
[2, -1, 1, 2, 2] -> true (0 -> 2 -> 3 -> 0)
[1, 1, 1, 1, 1] -> true (0 -> 1 -> 2 -> 3 -> 4 -> 0)
[1, 2] -> false (0 -> 1, and index 1 has no move: 2 % 2 == 0)Every index has at most one outgoing edge. A loop exists if walking from some index eventually comes back to an index already on the same walk.
next returns the index reached from i, or -1 when i has no move. Math.floorMod always returns a value in 0..n-1, so backward jumps that pass index 0 wrap to the end. Java's % keeps the sign of the left side (-1 % 5 is -1), which is why it isn't enough here. Values and sums are computed as long, so a large arr[i] can't overflow int.
import java.util.Arrays;
public class Main {
static int next(int[] arr, int i) {
long n = arr.length;
long step = arr[i] % n;
if (step == 0) return -1;
return (int) Math.floorMod(i + step, n);
}
public static void main(String[] args) {
int[] arr = {2, -1, 1, 2, 2};
int[] moves = new int[arr.length];
for (int i = 0; i < arr.length; i++) moves[i] = next(arr, i);
System.out.println(Arrays.toString(moves));
}
}[2, 0, 3, 0, 1]Brute force: walk n steps from every index
A walk that loops returns to its own start within n steps if the start is on the loop. Try every start.
public class Main {
static int next(int[] arr, int i) {
long n = arr.length;
long step = arr[i] % n;
if (step == 0) return -1;
return (int) Math.floorMod(i + step, n);
}
static boolean hasLoop(int[] arr) {
for (int start = 0; start < arr.length; start++) {
int cur = start;
for (int steps = 0; steps < arr.length; steps++) {
int j = next(arr, cur);
if (j == -1) break; // no move: this walk ends
if (j == start) return true;
cur = j;
}
}
return false;
}
public static void main(String[] args) {
System.out.println(hasLoop(new int[]{2, -1, 1, 2, 2}) + " " + hasLoop(new int[]{1, 1, 1, 1, 1}) + " " + hasLoop(new int[]{1, 2}));
}
}true true falseTime: O(n²). Space: O(1).
Optimal: one coloring pass
Give every index a color: unvisited, or the number of the walk that first reached it. Walk from each unvisited index, coloring as you go, and stop at an index that has no move or is already colored:
- colored by this walk: the walk came back to itself, so there's a loop.
- colored by an earlier walk: everything from there on was already explored and had no loop.
Every index is colored once, so the total work is linear. This is the same idea as the three-state cycle detection in a directed graph, simplified because each vertex has at most one edge.
walkOf[i] holds the number of the walk that colored index i, or -1 if no walk has reached it yet.
import java.util.Arrays;
public class Main {
static int next(int[] arr, int i) {
long n = arr.length;
long step = arr[i] % n;
if (step == 0) return -1;
return (int) Math.floorMod(i + step, n);
}
static boolean hasLoop(int[] arr) {
int[] walkOf = new int[arr.length];
Arrays.fill(walkOf, -1);
for (int start = 0; start < arr.length; start++) {
int cur = start;
while (walkOf[cur] == -1) {
walkOf[cur] = start;
int j = next(arr, cur);
if (j == -1) break;
cur = j;
}
if (walkOf[cur] == start && next(arr, cur) != -1) return true;
}
return false;
}
public static void main(String[] args) {
System.out.println(hasLoop(new int[]{2, -1, 1, 2, 2}) + " " + hasLoop(new int[]{1, 1, 1, 1, 1}) + " " + hasLoop(new int[]{1, 2}));
System.out.println(hasLoop(new int[]{-1, -1}) + " " + hasLoop(new int[]{3, 3, 3}));
}
}true true false
true falseTime: O(n). Space: O(n) for the colors.
When the inner loop stops, either cur has no move, or cur was already colored. The final check tells these apart: a colored cur reached through a move from this walk means the walk closed a loop. In [3, 3, 3], every 3 % 3 == 0, so no index moves at all.
Edge cases
- Self-loops: a jump that is a multiple of
nreturns to the same index, and the problem counts that as no move, so it can't form a loop. - LeetCode variant (457. Circular Array Loop): the loop must also move in one direction only. Stop a walk when the sign of
arr[cur]changes.
