Skip to content

Commit d18bf13

Browse files
committed
feat(dsa): fast and slow pointers approach to palindrome linked list
1 parent 38df36e commit d18bf13

File tree

5 files changed

+280
-9
lines changed

5 files changed

+280
-9
lines changed

datastructures/linked_lists/singly_linked_list/__init__.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,55 @@ def is_palindrome(self) -> bool:
576576

577577
return True
578578

579+
def is_palindrome_2(self) -> bool:
580+
"""
581+
Checks to see if a Linked list is a Palindrome.
582+
Returns True if it is, false otherwise.
583+
Uses two pointers approach to check if a linked list is a palindrome. First it finds the middle of the list using
584+
two pointers a fast and a slow pointer and then reverses the second half of the list. Once the second half is
585+
reversed, it compares the first half and the reversed second half
586+
587+
Complexity:
588+
We assume that n is the number of nodes in the linked list
589+
590+
Time O(n): we traverse the linked list to check for the palindrome property.
591+
592+
Space O(1): No extra space is used when traversing the linked list
593+
594+
Returns:
595+
bool: True if the linked list is a palindrome, false otherwise.
596+
"""
597+
598+
# An empty LinkedList or with 1 Node is a Palindrome
599+
if not self.head or not self.head.next:
600+
return True
601+
602+
# find the middle of the list using fast and slow pointers. The fast pointer will have gotten to the end of the
603+
# the linked list and the slow pointer will be at the middle of the linked list
604+
slow, fast = self.head, self.head
605+
while fast and fast.next:
606+
slow = slow.next
607+
fast = fast.next.next
608+
609+
# reverse the second half of the list
610+
prev = None
611+
while slow:
612+
nxt = slow.next
613+
slow.next = prev
614+
prev = slow
615+
slow = nxt
616+
617+
# now prev is the head of the reversed second half
618+
# compare the first half and the reversed second half
619+
left, right = self.head, prev
620+
while right:
621+
if left.data != right.data:
622+
return False
623+
left = left.next
624+
right = right.next
625+
626+
return True
627+
579628
def pairwise_swap(self) -> Optional[SingleNode]:
580629
"""
581630
Swaps nodes in pairs.

datastructures/linked_lists/singly_linked_list/test_singly_linked_palindrome.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import unittest
22

3+
import pytest
4+
35
from . import SinglyLinkedList
46

57

@@ -14,6 +16,190 @@ def test_1(self):
1416
actual = linked_list.is_palindrome()
1517
self.assertTrue(actual)
1618

19+
def test_2(self):
20+
"""should return true for [2, 4, 6, 4, 2]"""
21+
linked_list = SinglyLinkedList()
22+
data = [2, 4, 6, 4, 2]
23+
for d in data:
24+
linked_list.append(d)
25+
26+
actual = linked_list.is_palindrome()
27+
self.assertTrue(actual)
28+
29+
def test_3(self):
30+
"""should return true for [0, 3, 5, 5, 0]"""
31+
linked_list = SinglyLinkedList()
32+
data = [0, 3, 5, 5, 0]
33+
for d in data:
34+
linked_list.append(d)
35+
36+
actual = linked_list.is_palindrome()
37+
self.assertFalse(actual)
38+
39+
def test_4(self):
40+
"""should return true for [9, 7, 4, 4, 7, 9]"""
41+
linked_list = SinglyLinkedList()
42+
data = [9, 7, 4, 4, 7, 9]
43+
for d in data:
44+
linked_list.append(d)
45+
46+
actual = linked_list.is_palindrome()
47+
self.assertTrue(actual)
48+
49+
def test_5(self):
50+
"""should return true for [1,2,3,2,1]"""
51+
linked_list = SinglyLinkedList()
52+
data = [1,2,3,2,1]
53+
for d in data:
54+
linked_list.append(d)
55+
56+
actual = linked_list.is_palindrome()
57+
self.assertTrue(actual)
58+
59+
def test_6(self):
60+
"""should return true for [4,7,9,5,4]"""
61+
linked_list = SinglyLinkedList()
62+
data = [4,7,9,5,4]
63+
for d in data:
64+
linked_list.append(d)
65+
66+
actual = linked_list.is_palindrome()
67+
self.assertFalse(actual)
68+
69+
def test_7(self):
70+
"""should return true for [2,3,5,5,3,2]"""
71+
linked_list = SinglyLinkedList()
72+
data = [2,3,5,5,3,2]
73+
for d in data:
74+
linked_list.append(d)
75+
76+
actual = linked_list.is_palindrome()
77+
self.assertTrue(actual)
78+
79+
def test_8(self):
80+
"""should return true for [6,1,0,5,1,6]"""
81+
linked_list = SinglyLinkedList()
82+
data = [6,1,0,5,1,6]
83+
for d in data:
84+
linked_list.append(d)
85+
86+
actual = linked_list.is_palindrome()
87+
self.assertFalse(actual)
88+
89+
90+
def test_9(self):
91+
"""should return true for [3,6,9,8,4,8,9,6,3]"""
92+
linked_list = SinglyLinkedList()
93+
data = [3,6,9,8,4,8,9,6,3]
94+
for d in data:
95+
linked_list.append(d)
96+
97+
actual = linked_list.is_palindrome()
98+
self.assertTrue(actual)
99+
100+
class LinkedListIsPalindromeV2TestCase(unittest.TestCase):
101+
"""
102+
Tests to check if a linked list is a palindrome using fast and slow pointers approach
103+
"""
104+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
105+
def test_1(self):
106+
"""should return true for ["r", "a", "c", "e", "c", "a", "r"]"""
107+
linked_list = SinglyLinkedList()
108+
data = ["r", "a", "c", "e", "c", "a", "r"]
109+
for d in data:
110+
linked_list.append(d)
111+
112+
actual = linked_list.is_palindrome_2()
113+
self.assertTrue(actual)
114+
115+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
116+
def test_2(self):
117+
"""should return true for [2, 4, 6, 4, 2]"""
118+
linked_list = SinglyLinkedList()
119+
data = [2, 4, 6, 4, 2]
120+
for d in data:
121+
linked_list.append(d)
122+
123+
actual = linked_list.is_palindrome_2()
124+
self.assertTrue(actual)
125+
126+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
127+
def test_3(self):
128+
"""should return true for [0, 3, 5, 5, 0]"""
129+
linked_list = SinglyLinkedList()
130+
data = [0, 3, 5, 5, 0]
131+
for d in data:
132+
linked_list.append(d)
133+
134+
actual = linked_list.is_palindrome_2()
135+
self.assertFalse(actual)
136+
137+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
138+
def test_4(self):
139+
"""should return true for [9, 7, 4, 4, 7, 9]"""
140+
linked_list = SinglyLinkedList()
141+
data = [9, 7, 4, 4, 7, 9]
142+
for d in data:
143+
linked_list.append(d)
144+
145+
actual = linked_list.is_palindrome_2()
146+
self.assertTrue(actual)
147+
148+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
149+
def test_5(self):
150+
"""should return true for [1,2,3,2,1]"""
151+
linked_list = SinglyLinkedList()
152+
data = [1,2,3,2,1]
153+
for d in data:
154+
linked_list.append(d)
155+
156+
actual = linked_list.is_palindrome_2()
157+
self.assertTrue(actual)
158+
159+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
160+
def test_6(self):
161+
"""should return true for [4,7,9,5,4]"""
162+
linked_list = SinglyLinkedList()
163+
data = [4,7,9,5,4]
164+
for d in data:
165+
linked_list.append(d)
166+
167+
actual = linked_list.is_palindrome_2()
168+
self.assertFalse(actual)
169+
170+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
171+
def test_7(self):
172+
"""should return true for [2,3,5,5,3,2]"""
173+
linked_list = SinglyLinkedList()
174+
data = [2,3,5,5,3,2]
175+
for d in data:
176+
linked_list.append(d)
177+
178+
actual = linked_list.is_palindrome_2()
179+
self.assertTrue(actual)
180+
181+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
182+
def test_8(self):
183+
"""should return true for [6,1,0,5,1,6]"""
184+
linked_list = SinglyLinkedList()
185+
data = [6,1,0,5,1,6]
186+
for d in data:
187+
linked_list.append(d)
188+
189+
actual = linked_list.is_palindrome_2()
190+
self.assertFalse(actual)
191+
192+
@pytest.mark.linked_list_is_palindrome_fast_and_slow_pointer
193+
def test_9(self):
194+
"""should return true for [3,6,9,8,4,8,9,6,3]"""
195+
linked_list = SinglyLinkedList()
196+
data = [3,6,9,8,4,8,9,6,3]
197+
for d in data:
198+
linked_list.append(d)
199+
200+
actual = linked_list.is_palindrome_2()
201+
self.assertTrue(actual)
202+
17203

18204
if __name__ == "__main__":
19205
unittest.main()

poetry.lock

Lines changed: 38 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ readme = "README.md"
88

99
[tool.poetry.dependencies]
1010
python = "^3.11"
11-
pytest = "^7.4.3"
12-
1311

1412
[tool.poetry.group.dev.dependencies]
1513
ruff = "^0.3.5"
1614

15+
[tool.poetry.group.test.dependencies]
16+
pytest-benchmark = "^5.1.0"
17+
pytest = "^8.3.5"
18+
1719
[build-system]
1820
requires = ["poetry-core"]
1921
build-backend = "poetry.core.masonry.api"

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
markers =
3+
linked_list_is_palindrome_fast_and_slow_pointer: mark a test that this is a fast and slow pointer test for palindrome property of a linked list

0 commit comments

Comments
 (0)