-
Notifications
You must be signed in to change notification settings - Fork 4
/
zigzag-conversion.py
151 lines (137 loc) · 4.9 KB
/
zigzag-conversion.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
140
141
142
143
144
145
146
147
148
149
150
151
# 6. Zigzag Conversion
# 🟠 Medium
#
# https://leetcode.com/problems/zigzag-conversion/
#
# Tags: String
import timeit
from itertools import zip_longest
# Follow the steps of the problem exactly as they are given, easy to
# visualize solution but not very performant.
#
# Compute the direction we are going, down or up, and calculate the
# position of the character in the matrix based on the constraints given.
# When going down, character will be placed below the last one, when
# going up, character will be placed on the position diagonally right
# and up from the last one.
#
# Time complexity: O(n) - we iterate over the number of elements in the
# matrix 3 times, to create the matrix, to put each character in its
# position in the matrix, and to create the result string.
# Space complexity: O(n*m) - the matrix grows proportionally to both the
# size of the string and the number of rows.
#
# The space complexity could be optimized calculating the number of
# columns needed after half of the elements go in the same column and
# the other half in consecutive columns.
#
# Runtime: 336 ms, faster than 10.15%
# Memory Usage: 21.2 MB, less than 5.04%
class Matrix:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows >= len(s):
return s
matrix = [[""] * len(s) for _ in range(numRows)]
go_up = False
col, row = 0, 0
for c in s:
matrix[row][col] = c
if go_up:
# While moving up, increase the column and row count
col += 1
row -= 1
if row == 0:
go_up = False
else:
# When moving down, do not increase the column count,
# just check if we reached the bottom
row += 1
if row == numRows - 1:
go_up = True
# Merge the results one row at a time.
return "".join(["".join(row) for row in matrix])
# List comprehension is equivalent to:
# result = ""
# for row in matrix:
# result += "".join(row)
# return result
# This solution improves on the above one realizing that we don't really
# use the col position of the elements, we only care to know which row,
# and after which character, they are on. Instead of using a matrix, we
# can save time and space by using an array of strings as the matrix and
# appending each character to the corresponding row-string.
#
# Time complexity: O(n) - We go over each character in the string once.
# Space complexity: O(n) - We store all characters in the string in the
# rows array
#
# Runtime: 117 ms, faster than 30.80%
# Memory Usage: 13.9 MB, less than 96.00%
class Rows:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows > len(s):
return s
# Iterate over the characters of s appending them to the
# corresponding row.
rows = ["" for _ in range(numRows)]
i, down = 0, True
for c in s:
rows[i] += c
if down:
i += 1
if i == numRows - 1:
down = False
else:
i -= 1
if i == 0:
down = True
return "".join(rows)
# Using native functions, in this case zip_longest is probably not what
# an interviewer would be looking for, or even expect, but it is nice to
# be aware that it is a possibility, and that it tends to be more
# performant.
#
# Time complexity: O(n)
# Space complexity: O(n)
#
# Runtime: 70 ms, faster than 81.44%
# Memory Usage: 13.8 MB, less than 96.00%
class Zip:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows > len(s):
return s
n = numRows - 1
step = n * 2
res = s[::step]
for i in range(1, n):
for v, w in zip_longest(
s[i::step], s[step - i :: step], fillvalue=""
):
res += v + w
return res + s[n::step]
def test():
executors = [Matrix, Rows, Zip]
tests = [
["A", 1, "A"],
["AB", 1, "AB"],
["ABC", 1, "ABC"],
["PAYPALISHIRING", 3, "PAHNAPLSIIGYIR"],
["PAYPALISHIRING", 4, "PINALSIGYAHRPI"],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
sol = executor()
result = sol.convert(t[0], t[1])
exp = t[2]
assert result == exp, (
f"\033[93m» {result} <> {exp}\033[91m for"
+ f" test {col} using \033[1m{executor.__name__}"
)
stop = timeit.default_timer()
used = str(round(stop - start, 5))
cols = "{0:20}{1:10}{2:10}"
res = cols.format(executor.__name__, used, "seconds")
print(f"\033[92m» {res}\033[0m")
test()