Cycle Detection
Problem Statement
Write a Java program that extends a singly linked list implementation to detect if the list contains a cycle using Floyd’s Tortoise and Hare algorithm. The linked list consists of nodes, each containing an integer value and a reference to the next node. A cycle exists if a node’s next pointer points to an earlier node in the list. The program should return true if a cycle is detected and false otherwise, and test the implementation with both cyclic and acyclic lists of varying sizes, including empty lists, single-node lists, and multi-node lists with or without cycles. You can visualize this as checking if a chain of numbered cards loops back to an earlier card, using two pointers moving at different speeds to detect the loop.
Input:
- A singly linked list of integers (e.g., 1→2→3→4→2, where 4 points back to 2, forming a cycle). Output: A boolean indicating whether the list contains a cycle (true for cyclic, false for acyclic), along with a string representation of the list for clarity. Constraints:
- The list size is between 0 and 10^5.
- Node values are integers in the range [-10^9, 10^9].
- The list may be empty or contain a cycle at any position. Example:
- Input: 1→2→3→4→2 (cycle: 4→2)
- Output: true
- Explanation: A cycle exists as 4 points back to 2.
- Input: 1→2→3→null
- Output: false
- Explanation: No cycle, as the list terminates at null.
- Input: []
- Output: false
- Explanation: An empty list has no cycle.
Pseudocode
CLASS Node
SET value to integer
SET next to Node (null by default)
ENDCLASS
FUNCTION hasCycle(head)
IF head is null OR head.next is null THEN
RETURN false
ENDIF
SET slow to head
SET fast to head
WHILE fast is not null AND fast.next is not null
SET slow to slow.next
SET fast to fast.next.next
IF slow equals fast THEN
RETURN true
ENDIF
ENDWHILE
RETURN false
ENDFUNCTION
FUNCTION toString(head, limit)
IF head is null THEN
RETURN "[]"
ENDIF
CREATE result as new StringBuilder
SET current to head
SET count to 0
CREATE visited as new set
WHILE current is not null AND count < limit
IF visited contains current THEN
APPEND "->" + current.value + " (cycle)" to result
BREAK
ENDIF
ADD current to visited
APPEND current.value to result
IF current.next is not null THEN
APPEND " " to result
ENDIF
SET current to current.next
INCREMENT count
ENDWHILE
RETURN result as string
ENDFUNCTION
FUNCTION main()
SET testCases to array of linked lists (some cyclic, some acyclic)
FOR each testCase in testCases
PRINT test case details
CALL hasCycle(testCase.head)
PRINT whether cycle exists
ENDFOR
ENDFUNCTION
Algorithm Steps
- Define a
Nodeclass with: a. An integervalue. b. Anextpointer to the next node. - In
hasCycle(Floyd’s Tortoise and Hare): a. If head or head.next is null, return false (no cycle possible). b. Initializeslowandfastpointers to head. c. Whilefastandfast.nextare not null:- Move
slowone step,fasttwo steps. - If
slowequalsfast, a cycle exists (return true). d. Iffastreaches null, no cycle exists (return false).
- Move
- In
toString: a. If head is null, return "[]". b. Traverse the list with a limit to avoid infinite loops in cyclic lists. c. Use a set to detect cycles and indicate them in the output. d. Return space-separated values with cycle annotation if present. - In
main, test with empty, single-node, multi-node acyclic, and cyclic lists.
Java Implementation
import java.util.*;
public class CycleDetection {
// Node class for the linked list
static class Node {
int value;
Node next;
Node(int value) {
this.value = value;
this.next = null;
}
}
// Detects if the linked list has a cycle using Floyd’s algorithm
public boolean hasCycle(Node head) {
if (head == null || head.next == null) {
return false;
}
Node slow = head;
Node fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
// Converts linked list to string for output, handling cycles
public String toString(Node head) {
if (head == null) {
return "[]";
}
StringBuilder result = new StringBuilder();
Node current = head;
int count = 0;
int limit = 100; // Prevent infinite loop
Set<Node> visited = new HashSet<>();
while (current != null && count < limit) {
if (visited.contains(current)) {
result.append("->").append(current.value).append(" (cycle)");
break;
}
visited.add(current);
result.append(current.value);
if (current.next != null) {
result.append(" ");
}
current = current.next;
count++;
}
return result.toString();
}
// Helper class for test cases
static class TestCase {
Node head;
boolean hasCycle;
// Creates acyclic list from values
TestCase(int[] values) {
this.hasCycle = false;
if (values.length == 0) {
head = null;
return;
}
head = new Node(values[0]);
Node current = head;
for (int i = 1; i < values.length; i++) {
current.next = new Node(values[i]);
current = current.next;
}
}
// Creates cyclic list with cycle at specified position
TestCase(int[] values, int cyclePos) {
this.hasCycle = true;
if (values.length == 0) {
head = null;
return;
}
head = new Node(values[0]);
Node current = head;
Node cycleNode = null;
for (int i = 1; i < values.length; i++) {
current.next = new Node(values[i]);
current = current.next;
if (i == cyclePos) {
cycleNode = current;
}
}
if (cyclePos >= 0 && cyclePos < values.length) {
current.next = cycleNode; // Create cycle
}
}
}
// Main method to test cycle detection
public static void main(String[] args) {
CycleDetection detector = new CycleDetection();
// Test cases
TestCase[] testCases = {
new TestCase(new int[]{1, 2, 3, 4}, 2), // Cyclic: 1→2→3→4→3
new TestCase(new int[]{1, 2, 3}), // Acyclic: 1→2→3
new TestCase(new int[]{}), // Empty
new TestCase(new int[]{5}), // Single node
new TestCase(new int[]{1, 2, 3, 4, 5}, 0) // Cyclic: 1→2→3→4→5→1
};
// Run test cases
for (int i = 0; i < testCases.length; i++) {
TestCase test = testCases[i];
System.out.println("Test case " + (i + 1) + ":");
System.out.println("Input list: " + detector.toString(test.head));
boolean result = detector.hasCycle(test.head);
System.out.println("Has cycle: " + result + "\n");
}
}
}
Output
Running the main method produces:
Test case 1:
Input list: 1 2 3 4->3 (cycle)
Has cycle: true
Test case 2:
Input list: 1 2 3
Has cycle: false
Test case 3:
Input list: []
Has cycle: false
Test case 4:
Input list: 5
Has cycle: false
Test case 5:
Input list: 1 2 3 4 5->1 (cycle)
Has cycle: true
Explanation:
- Test case 1: List 1→2→3→4 with 4→3 forms a cycle, returns true.
- Test case 2: Acyclic list 1→2→3, returns false.
- Test case 3: Empty list, returns false.
- Test case 4: Single node 5, returns false.
- Test case 5: List 1→2→3→4→5 with 5→1 forms a cycle, returns true.
How It Works
- Node: Stores an integer value and a next pointer.
- hasCycle (Floyd’s Tortoise and Hare):
- Returns false if list is empty or has one node.
- Uses
slow(moves one step) andfast(moves two steps) pointers. - If
slowmeetsfast, a cycle exists. - If
fastreaches null, no cycle exists.
- toString:
- Handles cycles by tracking visited nodes and limiting traversal.
- Outputs values with spaces, appending cycle annotation if detected.
- Example Trace (Test case 1):
- Input: 1→2→3→4→3 (cycle at 3).
- slow=1, fast=1.
- Step 1: slow=2, fast=3.
- Step 2: slow=3, fast=4.
- Step 3: slow=4, fast=3.
- Step 4: slow=3, fast=3 (meet, cycle detected).
- Return: true.
- Main Method: Tests cyclic (cycle at different positions), acyclic, empty, and single-node lists.
Complexity Analysis Table
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Has Cycle | O(n) | O(1) |
| To String | O(n) | O(n) |
Note:
- n is the number of nodes in the list.
- Time complexity: O(n) for hasCycle (Floyd’s algorithm traverses list); O(n) for toString (traverse with cycle detection).
- Space complexity: O(1) for hasCycle (two pointers); O(n) for toString (visited set and StringBuilder).
- Worst case: O(n) time, O(n) space for large lists in output.
✅ Tip: Use Floyd’s Tortoise and Hare algorithm for efficient cycle detection with O(1) space. Test with cycles at different positions to ensure robustness.
⚠ Warning: Ensure pointers are checked for null to avoid null pointer exceptions. Be cautious when printing cyclic lists to avoid infinite loops.