-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathp06_palindrome.py
139 lines (109 loc) · 3.07 KB
/
p06_palindrome.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import time
from chapter_02.linked_list import LinkedList
def is_palindrome(ll):
fast = slow = ll.head
stack = []
while fast and fast.next:
stack.append(slow.value)
slow = slow.next
fast = fast.next.next
if fast:
slow = slow.next
while slow:
top = stack.pop()
if top != slow.value:
return False
slow = slow.next
return True
def is_palindrome_constant_space(ll):
"""
Constant(O(1)) space solution
"""
# find the list center via the runner technique
slow = ll.head
if not slow or not slow.next:
return True
fast = slow.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# unlink left and right halves of the list
right_head = slow.next
slow.next_node = None
# reverse the right half of the list
tail = reverse(right_head)
# iterate over nodes from the outside in
left, right = ll.head, tail
result = True
while left and right:
if left.value != right.value:
result = False
break
left = left.next
right = right.next
# undo state changes
reverse(tail)
slow.next_node = right_head
return result
def reverse(node):
"""
reverses a linked list,
returns the input list's
tail node as the new head
Time : O(N)
Space: O(1)
"""
previous_node = None
while node:
# keep track of the next node
next_node = node.next
# point the current node backwards
node.next = previous_node
# advance pointers
previous_node = node
node = next_node
return previous_node
def is_palindrome_recursive(ll):
def get_len(node):
if not node:
return 0
else:
return 1 + get_len(node.next)
def recursive_transverse(node, length):
if not node or length == 0: # even list
return True, node
elif length == 1: # odd list
return True, node.next
_is_palindrome, fwd_node = recursive_transverse(node.next, length - 2)
if not _is_palindrome or not fwd_node:
return False, None
if node.value == fwd_node.value:
return True, fwd_node.next
else:
return False, None
return recursive_transverse(ll.head, get_len(ll.head))[0]
test_cases = [
([1, 2, 3, 4, 3, 2, 1], True),
([1], True),
(["a", "a"], True),
("aba", True),
([], True),
([1, 2, 3, 4, 5], False),
([1, 2], False),
]
testable_functions = [
is_palindrome,
is_palindrome_constant_space,
is_palindrome_recursive,
]
def test_is_palindrome():
for f in testable_functions:
start = time.perf_counter()
for values, expected in test_cases:
print(f"{f.__name__}: {values}")
for _ in range(100):
assert f(LinkedList(values)) == expected
duration = time.perf_counter() - start
print(f"{f.__name__} {duration * 1000:.1f}ms")
if __name__ == "__main__":
test_is_palindrome()