-
Notifications
You must be signed in to change notification settings - Fork 4
/
reorder-list.py
126 lines (107 loc) · 3.63 KB
/
reorder-list.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
# 143. Reorder List
# 🟠 Medium
#
# https://leetcode.com/problems/reorder-list/
#
# Tags: Linked List - Two Pointers - Stack - Recursion
import timeit
from collections import deque
from typing import Optional
from data import ListNode, deserializeListToLinkedList, serializeLinkedList
# If we can use extra memory, store all nodes in a deque and pop
# alternatively from left/right reconnecting the nodes.
#
# Time complexity: O(n) - We travel through the linked list, then the deque.
# Space complexity: O(n) - We store all ListNodes in the deque.
#
# Runtime 56 ms Beats 54%
# Memory 24.86 MB Beats 5%
class Deque:
def reorderList(self, head: Optional[ListNode]) -> None:
"""
Do not return anything, modify head in-place instead.
"""
if not head or not head.next:
return
deq = deque()
current = head.next
while current:
deq.append(current)
current = current.next
i = 0
current = head
while deq:
current.next = deq.popleft() if i % 2 else deq.pop()
current = current.next
i += 1
# The last element should not point to anything
current.next = None
# If are not allowed to use any extra memory, we can do it by finding
# the middle, reversing the second half of the linked list, then merging
# the non-reversed first half with the second, reversed half.
#
# Time complexity: O(n) - We visit all nodes a linear number of times.
# Space complexity; O(1) - Only a fixed number of variables are kept.
#
# Runtime 43 ms Beats 97%
# Memory 24.1 MB Beats 50%
class NoExtraMemory:
def _mergeLists(self, head_a, head_b):
tail = head_a
head = head_a
head_a = head_a.next
while head_b:
tail.next = head_b
tail = tail.next
head_b = head_b.next
if head_a:
head_a, head_b = head_b, head_a
return head
def reorderList(self, head: Optional[ListNode]) -> None:
if not head or not head.next or not head.next.next:
return
# Split the lists into first and second halves
fast, slow = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
middle = slow.next
slow.next = None
# Reverse the second half
last, current = None, middle
while current:
next = current.next
current.next = last
last = current
current = next
tail = last
# Merge the two halves
return self._mergeLists(head, tail)
def test():
executors = [Deque, NoExtraMemory]
tests = [
[[1], [1]],
[[1, 2], [1, 2]],
[[1, 2, 3], [1, 3, 2]],
[[1, 2, 3, 4], [1, 4, 2, 3]],
[[1, 2, 3, 4, 5], [1, 5, 2, 4, 3]],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
sol = executor()
head = deserializeListToLinkedList(t[0])
# Modify in place
sol.reorderList(head)
exp = t[1]
serialized_result = serializeLinkedList(head)
assert serialized_result == exp, (
f"\033[93m» {serialized_result} <> {exp}\033[91m for"
+ f" test {col} using \033[1m{executor.__name__}"
)
stop = timeit.default_timer()
used = str(round(stop - start, 5))
res = "{0:20}{1:10}{2:10}".format(executor.__name__, used, "seconds")
print(f"\033[92m» {res}\033[0m")
test()