Lesson 35 of 164
Detect a Cycle in a Linked List
Given the head of a linked list, return true if the list has a cycle: following next pointers eventually leads back to a node that was already visited. Otherwise return false.
LeetCode: 141. Linked List Cycle
Example 1
Input: 3 -> 2 -> 0 -> -4, and -4 points back to 2
Output: truegraph LR
A["3 (index 0)"] --> B["2 (index 1)"] --> C["0 (index 2)"] --> D["-4 (index 3)"]
D --> BExample 2
Input: 1 -> 2 -> 3 -> null
Output: falseBuilding a list with a cycle
In Java, next is a reference to a node object, and null marks the end of the list. Assigning the last node's next to an earlier node creates a cycle. The build helper below keeps every node in an array so the examples can name a node by its index, and points the last node at index pos.
public class Main {
static class Node {
int val;
Node next; // null marks the end of the list
Node(int val) {
this.val = val;
}
}
// Builds a list from values; the last node points to index pos (or to null when pos is -1).
static Node[] build(int[] values, int pos) {
int n = values.length;
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
nodes[i] = new Node(values[i]);
}
for (int i = 0; i + 1 < n; i++) {
nodes[i].next = nodes[i + 1];
}
if (pos >= 0) nodes[n - 1].next = nodes[pos];
return nodes;
}
public static void main(String[] args) {
Node[] list = build(new int[]{3, 2, 0, -4}, 1);
for (int i = 0; i < list.length; i++) {
Node next = list[i].next;
System.out.println("index " + i + ": val " + list[i].val + ", next val " + (next == null ? "none" : next.val));
}
}
}index 0: val 3, next val 2
index 1: val 2, next val 0
index 2: val 0, next val -4
index 3: val -4, next val 2Brute force: remember every node you've visited
Walk the list and store each node in a HashSet. If we reach a node that's already in the set, we've gone around a cycle. If we reach null, there's no cycle.
Walkthrough with example 1:
| current index | in set? | set after |
|---|---|---|
| 0 | no | {0} |
| 1 | no | {0, 1} |
| 2 | no | {0, 1, 2} |
| 3 | no | {0, 1, 2, 3} |
| 1 | yes, return true |
import java.util.HashSet;
import java.util.Set;
public class Main {
static class Node {
int val;
Node next; // null marks the end of the list
Node(int val) {
this.val = val;
}
}
// Builds a list from values; the last node points to index pos (or to null when pos is -1).
static Node[] build(int[] values, int pos) {
int n = values.length;
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
nodes[i] = new Node(values[i]);
}
for (int i = 0; i + 1 < n; i++) {
nodes[i].next = nodes[i + 1];
}
if (pos >= 0) nodes[n - 1].next = nodes[pos];
return nodes;
}
static boolean hasCycle(Node head) {
Set<Node> seen = new HashSet<>(); // Node does not override equals, so the set compares identity
for (Node cur = head; cur != null; cur = cur.next) {
if (!seen.add(cur)) {
return true; // visited before
}
}
return false;
}
public static void main(String[] args) {
Node[] looped = build(new int[]{3, 2, 0, -4}, 1);
Node[] straight = build(new int[]{1, 2, 3}, -1);
System.out.println(hasCycle(looped[0]) + " " + hasCycle(straight[0]));
}
}true falseTime: O(n). Space: O(n) for the set.
Store the nodes, not their values. Two different nodes can hold the same value without forming a cycle. Node does not override equals or hashCode, so the set compares object identity. A node class that compared by value would report a cycle for any repeated value.
Optimal: Floyd's fast and slow pointers
Move slow one node at a time and fast two nodes at a time.
- If there's no cycle,
fastreaches the end. - If there's a cycle, both pointers end up inside it. Each step,
fastgains one node onslow, so the gap between them shrinks by one per step until they land on the same node. That takes at most one trip around the cycle.
Walkthrough with example 1 (indexes shown):
| step | slow | fast |
|---|---|---|
| start | 0 | 0 |
| 1 | 1 | 2 |
| 2 | 2 | 1 (3 → back to 1) |
| 3 | 3 | 3 (1 → 2 → 3) |
They meet at index 3, so there's a cycle.
public class Main {
static class Node {
int val;
Node next; // null marks the end of the list
Node(int val) {
this.val = val;
}
}
// Builds a list from values; the last node points to index pos (or to null when pos is -1).
static Node[] build(int[] values, int pos) {
int n = values.length;
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
nodes[i] = new Node(values[i]);
}
for (int i = 0; i + 1 < n; i++) {
nodes[i].next = nodes[i + 1];
}
if (pos >= 0) nodes[n - 1].next = nodes[pos];
return nodes;
}
static boolean hasCycle(Node head) {
Node slow = head, fast = head;
while (fast != null && fast.next != null) { // fast falls off the end when there is no cycle
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true; // they met
}
return false;
}
public static void main(String[] args) {
Node[] looped = build(new int[]{3, 2, 0, -4}, 1);
Node[] straight = build(new int[]{1, 2, 3}, -1);
Node[] selfLoop = build(new int[]{1}, 0);
System.out.println(hasCycle(looped[0]) + " " + hasCycle(straight[0]) + " " + hasCycle(selfLoop[0]));
System.out.println(hasCycle(null));
}
}true false true
falseTime: O(n). Space: O(1).
The condition fast != null && fast.next != null is checked before fast.next.next is read, so fast can stop after one or two moves without a NullPointerException. An empty list (null head) skips the loop and returns false.
Follow-up: where does the cycle start?
(LeetCode 142.) After the pointers meet, move one pointer back to the head and advance both one node at a time. They meet again at the first node of the cycle:
public class Main {
static class Node {
int val;
Node next; // null marks the end of the list
Node(int val) {
this.val = val;
}
}
// Builds a list from values; the last node points to index pos (or to null when pos is -1).
static Node[] build(int[] values, int pos) {
int n = values.length;
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
nodes[i] = new Node(values[i]);
}
for (int i = 0; i + 1 < n; i++) {
nodes[i].next = nodes[i + 1];
}
if (pos >= 0) nodes[n - 1].next = nodes[pos];
return nodes;
}
static Node cycleStart(Node head) {
Node slow = head, fast = head;
while (true) {
if (fast == null || fast.next == null) return null; // no cycle
slow = slow.next;
fast = fast.next.next;
if (slow == fast) break;
}
Node a = head;
while (a != slow) {
a = a.next;
slow = slow.next;
}
return a;
}
public static void main(String[] args) {
Node[] nodes = build(new int[]{0, 1, 2, 3}, 1); // the values equal the indexes
Node start = cycleStart(nodes[0]);
System.out.println(start == null ? "none" : "index " + start.val);
System.out.println(cycleStart(build(new int[]{0, 1, 2}, -1)[0]));
}
}index 1
nullThis works because the distance from the head to the cycle's start equals the distance from the meeting point to the cycle's start, measured forward around the cycle. The second call shows the result for a list without a cycle (null).
Cycles and memory
The garbage collector reclaims objects that are unreachable from the running program, including a cycle that nothing points to from outside. A list with a cycle therefore needs no special cleanup. Use == to compare nodes by identity.
