Skip to content

Commit 1496a09

Browse files
committed
[Feature] Added Linked List cycle detection and cycle start detection.
1 parent 1ab608e commit 1496a09

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ $ coverage report -m
3838
### Data Structures
3939
* [LRU Cache](structures/lru_cache.py)
4040
* [Naïve Tree](structures/naive_tree.py)
41+
* [In-Order Traversal](structures/naive_tree.py)
42+
* [Pre-Order Traversal](structures/naive_tree.py)
43+
* [Post-Order Traversal](structures/naive_tree.py)
4144
* [Binary Search Tree](structures/binary_search_tree.py)
4245
* [Hash Map](structures/hash_map.py)
4346
* [DJB2 Hash Function](structures/hash_map.py)
@@ -46,6 +49,9 @@ $ coverage report -m
4649
* [Heap](structures/heap.py)
4750
* [Trie](structures/trie.py)
4851
* [Priority Queue](structures/priority_queue.py)
52+
* [Linked List](structures/linked_list.py)
53+
* [Contains Cycles](structures/linked_list.py)
54+
* [Find Start of Cycle](structures/linked_list.py)
4955

5056
### Algorithms
5157

structures/linked_list.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
class ListNode():
2+
3+
def __init__(self, val, next=None):
4+
self.val = val
5+
self.next = next
6+
7+
def contains_cycle(self):
8+
# the list is only 1 node long, which cannot contain a cycle
9+
if not self.next:
10+
return False
11+
slow_pointer = self.next
12+
fast_pointer = self.next.next
13+
14+
while slow_pointer is not fast_pointer:
15+
if slow_pointer:
16+
slow_pointer = slow_pointer.next
17+
if fast_pointer and fast_pointer.next:
18+
fast_pointer = fast_pointer.next.next
19+
if slow_pointer is None:
20+
return False
21+
else:
22+
return True
23+
24+
def get_beginning_of_cycle_if_exists(self):
25+
# the list is only 1 node long, which cannot contain a cycle
26+
if not self.next:
27+
return None
28+
slow_pointer = self.next
29+
fast_pointer = self.next.next
30+
31+
while slow_pointer is not fast_pointer:
32+
if slow_pointer:
33+
slow_pointer = slow_pointer.next
34+
if fast_pointer and fast_pointer.next:
35+
fast_pointer = fast_pointer.next.next
36+
if slow_pointer is None:
37+
return None
38+
# reset slow pointer
39+
slow_pointer = self
40+
# move the pointers again at same speed, will meet at start
41+
while slow_pointer is not fast_pointer:
42+
print(slow_pointer.val, fast_pointer.val)
43+
slow_pointer = slow_pointer.next
44+
fast_pointer = fast_pointer.next
45+
46+
return slow_pointer

tests/test_linked_list.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import unittest
2+
from structures.linked_list import ListNode
3+
4+
class TestLinkedList(unittest.TestCase):
5+
6+
def test_ll_contains_cycle(self):
7+
head = ListNode(3)
8+
head.next = ListNode(4)
9+
head.next.next = ListNode(5)
10+
cycle_start = ListNode(6)
11+
head.next.next.next = cycle_start
12+
cycle_start.next = ListNode(7)
13+
cycle_start.next.next = ListNode(8)
14+
cycle_start.next.next.next = cycle_start
15+
16+
self.assertTrue(head.contains_cycle())
17+
18+
def test_ll_no_cycle(self):
19+
head = ListNode(3)
20+
head.next = ListNode(4)
21+
head.next.next = ListNode(5)
22+
cycle_start = ListNode(6)
23+
head.next.next.next = cycle_start
24+
cycle_start.next = ListNode(7)
25+
cycle_start.next.next = ListNode(8)
26+
27+
self.assertFalse(head.contains_cycle())
28+
29+
def test_ll_cycle_on_single_node(self):
30+
self.assertFalse(ListNode(1).contains_cycle())
31+
32+
def test_ll_get_cycle_start(self):
33+
head = ListNode(3)
34+
head.next = ListNode(4)
35+
head.next.next = ListNode(5)
36+
cycle_start = ListNode(6)
37+
head.next.next.next = cycle_start
38+
cycle_start.next = ListNode(7)
39+
cycle_start.next.next = ListNode(8)
40+
cycle_start.next.next.next = cycle_start
41+
42+
self.assertEqual(cycle_start, head.get_beginning_of_cycle_if_exists())
43+
44+
def test_ll_get_cycle_start_no_cycle(self):
45+
head = ListNode(3)
46+
head.next = ListNode(4)
47+
head.next.next = ListNode(5)
48+
cycle_start = ListNode(6)
49+
head.next.next.next = cycle_start
50+
cycle_start.next = ListNode(7)
51+
cycle_start.next.next = ListNode(8)
52+
53+
self.assertEqual(None, head.get_beginning_of_cycle_if_exists())
54+
55+
def test_ll_get_cycle_start_on_single_node(self):
56+
self.assertEqual(None, ListNode(1).get_beginning_of_cycle_if_exists())

0 commit comments

Comments
 (0)