Алгоритм Флойда «черепаха і заєць» для пошуку циклу в зв'язному списку
Маємо зв'язний список, початок якого позначено як head, і в якому цикл може бути присутнім, а може й ні. Наприклад:

Тут нам потрібно знайти точку C, тобто початок циклу.
- Чи структура задає функціональний перехід — з кожного стану рівно один наступний (зв'язний список, ітерація )?
- Чи потрібно виявити цикл, використовуючи пам'яті, без множини відвіданих вершин?
- Чи потрібно знайти саме початок циклу, а не лише факт його наявності?
Запропонований алгоритм
Цей алгоритм називається алгоритмом Флойда для пошуку циклу, або алгоритмом «черепаха і заєць». Щоб з'ясувати початок циклу, спершу нам потрібно зрозуміти, чи цикл узагалі існує. Це складається з двох кроків:
- Визначити наявність циклу.
- Знайти початок циклу.
Крок 1: наявність циклу
- Візьмемо два вказівники, і .
- Спочатку обидва вказуватимуть на head зв'язного списку.
- рухатиметься на один крок за раз.
- рухатиметься на два кроки за раз (удвічі швидше за вказівник ).
- Перевіряємо, чи в якийсь момент вони вкажуть на одну й ту саму вершину, перш ніж один із них (або обидва) досягне null.
- Якщо в якийсь момент свого шляху вони вказують на одну й ту саму вершину, це означає, що цикл у зв'язному списку справді існує.
- Якщо ж ми отримуємо null, це означає, що зв'язний список не містить циклу.

Тепер, коли ми з'ясували, чи присутній цикл у зв'язному списку, на наступному кроці нам потрібно знайти початок циклу, тобто C.
Крок 2: початок циклу
- Скинемо вказівник на head зв'язного списку.
- Рухаємо обидва вказівники на один крок за раз.
- Точка, у якій вони зустрінуться, і буде початком циклу.
// Наявність циклу
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
// Припускаємо, що цикл присутній і slow та fast вказують на точку своєї зустрічі
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return slow; // початок циклу.
Чому це працює
Крок 1: наявність циклу
Оскільки вказівник рухається удвічі швидше за , можна сказати, що в будь-який момент часу пройшов би удвічі більшу відстань, ніж . Також можна вивести, що різниця між відстанями, які пройшли обидва вказівники, зростає на .
slow: 0 --> 1 --> 2 --> 3 --> 4 (пройдена відстань)
fast: 0 --> 2 --> 4 --> 6 --> 8 (пройдена відстань)
diff: 0 --> 1 --> 2 --> 3 --> 4 (різниця між пройденими обома вказівниками відстанями)
Нехай позначає довжину циклу, а — кількість кроків, потрібних повільному вказівнику, щоб дістатися входу в цикл. Існує таке додатне ціле число (), що . Коли повільний вказівник пройшов кроків, а швидкий вказівник подолав кроків, обидва вказівники опиняються всередині циклу. У цей момент між ними відстань . Оскільки довжина циклу дорівнює , це означає, що вони перебувають в одній і тій самій точці циклу, тобто зустрічаються.
Крок 2: початок циклу
Спробуймо обчислити відстань, яку пройшли обидва вказівники до моменту їхньої зустрічі всередині циклу.

,
,
- — це загальна відстань, яку пройшов повільний вказівник.
- — це загальна відстань, яку пройшов швидкий вказівник.
- — це кількість кроків, які обом вказівникам потрібно зробити, щоб увійти в цикл.
- — це відстань між C і G, тобто відстань між початком циклу та точкою зустрічі обох вказівників.
- — це кількість разів, що повільний вказівник зробив повний оберт усередині циклу, починаючи й закінчуючи в C.
- — це кількість разів, що швидкий вказівник зробив повний оберт усередині циклу, починаючи й закінчуючи в C.
Розв'язавши формулу, отримуємо:
де — ціле число
По суті, це означає, що кроків — це те саме, що зробити деяку кількість повних обертів у циклі та пройти кроків назад. Оскільки швидкий вказівник уже на кроків випереджає вхід у цикл, то, якщо він зробить ще кроків, він опиниться на вході в цикл. А оскільки ми поставили повільний вказівник на початок зв'язного списку, то після кроків він також опиниться на вході в цикл. Отже, якщо обидва пройдуть кроків, вони обидва зустрінуться на вході в цикл.
Задачі:
- Linked List Cycle (EASY)
- Happy Number (Easy)
- Find the Duplicate Number (Medium)
- Linked List Cycle II