Skip to content

Commit a6cbc81

Browse files
committed
refactor(data-structures, linked-lists): split circular linked list
1 parent 1d4c91e commit a6cbc81

3 files changed

Lines changed: 90 additions & 38 deletions

File tree

datastructures/linked_lists/circular/__init__.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -206,34 +206,54 @@ def split_list(self) -> Optional[Tuple[CircularNode, Optional[CircularNode]]]:
206206
Returns:
207207
Tuple: tuple with two circular linked lists
208208
"""
209-
size = len(self)
210-
211-
if size == 0:
209+
# If there is no head node, there is nothing more to do here
210+
if not self.head:
212211
return None
213-
if size == 1:
214-
return self.head, None
215212

216-
mid = size // 2
217-
count = 0
213+
# Get the middle of the linked list
214+
middle_node = self.middle_node()
218215

219-
previous: Optional[CircularNode] = None
220-
current = self.head
216+
# Set the head node to head_one and head_two will be the head node of the second linked list
217+
head_one = self.head
218+
head_two = middle_node.next
219+
# Set the middle node's next pointer to cycle back to the head_one node
220+
middle_node.next = head_one
221221

222-
while current and count < mid:
223-
count += 1
224-
previous = current
222+
# Assign a pointer to the second head node and move it along the linked list as long as it's next pointer is not
223+
# equal to the head node
224+
current = head_two
225+
while current.next is not self.head:
225226
current = current.next
226227

227-
previous.next = self.head
228+
# Now since we are at the tail of the linked list, we assign the next pointer to point to the second head node
229+
current.next = head_two
228230

229-
second_list = CircularLinkedList()
230-
while current.next != self.head:
231-
second_list.append(current.data)
232-
current = current.next
231+
# Now we have split the linked list into two halves.
232+
return head_one, head_two
233+
234+
def middle_node(self) -> Optional[CircularNode]:
235+
"""
236+
Traverse the linked list to find the middle node. For a circular linked list, the tail node points back to the
237+
head node, to prevent this from continuously looping, we have to break the cycle once the tail node's next pointer
238+
points back to the head node
239+
Time Complexity: O(n) where n is the number of nodes in the linked list
240+
Space Complexity: O(1) as constant extra space is needed
241+
Return:
242+
CircularNode: middle node of linked list
243+
"""
244+
if not self.head:
245+
return None
246+
247+
fast_pointer, slow_pointer = self.head, self.head
233248

234-
second_list.append(current.data)
249+
while (
250+
fast_pointer.next is not self.head
251+
and fast_pointer.next.next is not self.head
252+
):
253+
slow_pointer = slow_pointer.next
254+
fast_pointer = fast_pointer.next.next
235255

236-
return self.head, second_list
256+
return slow_pointer
237257

238258
def is_palindrome(self) -> bool:
239259
pass

datastructures/linked_lists/circular/test_circular_linked_list.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,5 @@ def test_1(self):
5555
self.assertEqual(expected, list(circular_linked_list))
5656

5757

58-
class CircularLinkedListSplitListTestCase(unittest.TestCase):
59-
def test_1(self):
60-
"""should split a linked list [1,2,3,4,5,6] to become ([1,2,3],[4,5,6])"""
61-
data = [1, 2, 3, 4, 5, 6]
62-
expected = ([1, 2, 3], [4, 5, 6])
63-
circular_linked_list = CircularLinkedList()
64-
65-
for d in data:
66-
circular_linked_list.append(d)
67-
68-
result = circular_linked_list.split_list()
69-
self.assertIsNotNone(result)
70-
71-
first_list_head, second_list_head = result
72-
73-
self.assertEqual(expected[0], list(first_list_head))
74-
self.assertEqual(expected[1], list(second_list_head))
75-
76-
7758
if __name__ == "__main__":
7859
unittest.main()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import unittest
2+
from typing import List, Tuple
3+
from parameterized import parameterized
4+
from datastructures.linked_lists.circular import CircularLinkedList
5+
from datastructures.linked_lists.circular.node import CircularNode
6+
7+
8+
CIRCULAR_LINKED_LIST_SPLIT_LIST_TEST_CASES = [
9+
([1, 2, 3, 4, 5, 6], ([1, 2, 3], [4, 5, 6])),
10+
([], ([], [])),
11+
([1, 5, 7], ([1, 5], [7])),
12+
([2, 6, 1, 5], ([2, 6], [1, 5])),
13+
([3, 1, 4, 2, 5], ([3, 1, 4], [2, 5])),
14+
([8, 10, 12, 14, 16, 18], ([8, 10, 12], [14, 16, 18])),
15+
([9, 10], ([9], [10])),
16+
]
17+
18+
19+
class CircularLinkedListSplitListTestCase(unittest.TestCase):
20+
def assert_data(self, head: CircularNode, expected: List[int]):
21+
current = head
22+
actual = []
23+
while current:
24+
actual.append(current.data)
25+
if current.next is head:
26+
break
27+
current = current.next
28+
29+
self.assertListEqual(expected, actual)
30+
31+
@parameterized.expand(CIRCULAR_LINKED_LIST_SPLIT_LIST_TEST_CASES)
32+
def test_split_list(self, data: List[int], expected: Tuple[List[int], List[int]]):
33+
circular_linked_list = CircularLinkedList()
34+
if not data:
35+
result = circular_linked_list.split_list()
36+
self.assertIsNone(result)
37+
return
38+
39+
for d in data:
40+
circular_linked_list.append(d)
41+
42+
result = circular_linked_list.split_list()
43+
self.assertIsNotNone(result)
44+
45+
first_list_head, second_list_head = result
46+
self.assert_data(first_list_head, expected[0])
47+
self.assert_data(second_list_head, expected[1])
48+
49+
50+
if __name__ == "__main__":
51+
unittest.main()

0 commit comments

Comments
 (0)