-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathlexicographically-smallest-equivalent-string.py
128 lines (115 loc) · 4.69 KB
/
lexicographically-smallest-equivalent-string.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
# 1061. Lexicographically Smallest Equivalent String
# 🟠 Medium
#
# https://leetcode.com/problems/lexicographically-smallest-equivalent-string/
#
# Tags: String - Union Find
import timeit
# Use union find by rank where the rank is the reversed lexicographical
# order of the characters to be joined.
#
# Time complexity: O(n) - Where n is the number of characters in the
# input, we visit all characters in s1 and s2 to construct the union
# find structure, then we visit all characters in base str to find their
# lexicographically smallest equivalents, the find parent operation runs
# in amortized O(1) because it uses path compression.
# Space complexity: O(1) - The parents dictionary can grow to size 26.
#
# Runtime 39 ms Beats 93.68%
# Memory 13.9 MB Beats 94.83%
class UseHashMap:
def smallestEquivalentString(self, s1: str, s2: str, baseStr: str) -> str:
# A function that finds the representative of a disjoint set, in
# this case the representative consists of the lexicographically
# smallest member of the group.
def findParent(a: str) -> str:
if a not in parents:
parents[a] = a
return parents[a]
if parents[a] == a:
return a
parents[a] = findParent(parents[a])
return parents[a]
# Merge two disjoint sets into one with the lexicographically
# smaller parent as the parent of the merged result.
def union(a: str, b: str) -> None:
pa, pb = findParent(a), findParent(b)
if ord(pa) > ord(pb):
pa, pb = pb, pa
parents[pb] = pa
parents = {}
# Find all the disjoint groups.
for i in range(len(s1)):
union(s1[i], s2[i])
# Use the disjoint groups to update every character in base str
# to the lexicographically lowest equivalent.
res = [None] * len(baseStr)
for i in range(len(baseStr)):
res[i] = findParent(baseStr[i])
return "".join(res)
# Improve the previous solution using an array instead of a dictionary.
#
# Time complexity: O(n) - Where n is the number of characters in the
# input, we visit all characters in s1 and s2 to construct the union
# find structure, then we visit all characters in base str to find their
# lexicographically smallest equivalents, the find parent operation runs
# in amortized O(1) because it uses path compression.
# Space complexity: O(1) - The parents array is of size 26.
#
# Runtime 27 ms Beats 99.43%
# Memory 13.9 MB Beats 94.83%
class UseArray:
def smallestEquivalentString(self, s1: str, s2: str, baseStr: str) -> str:
# A function that finds the representative of a disjoint set, in
# this case the representative consists of the lexicographically
# smallest member of the group.
def findParent(a: str) -> str:
idx = ord(a) - 97
if parents[idx] != a:
parents[idx] = findParent(parents[idx])
return parents[idx]
# Merge two disjoint sets into one with the lexicographically
# smaller parent as the parent of the merged result.
def union(a: str, b: str) -> None:
pa, pb = findParent(a), findParent(b)
if pa > pb:
pa, pb = pb, pa
parents[ord(pb) - 97] = pa
# Initialize an array of parents.
parents = [chr(i + ord("a")) for i in range(26)]
# Find all the disjoint groups.
for i in range(len(s1)):
union(s1[i], s2[i])
# Use the disjoint groups to update every character in base str
# to the lexicographically lowest equivalent.
res = [None] * len(baseStr)
for i in range(len(baseStr)):
res[i] = findParent(baseStr[i])
return "".join(res)
def test():
executors = [
UseHashMap,
UseArray,
]
tests = [
["parker", "morris", "parser", "makkek"],
["hello", "world", "hold", "hdld"],
["leetcode", "programs", "sourcecode", "aauaaaaada"],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
sol = executor()
result = sol.smallestEquivalentString(t[0], t[1], t[2])
exp = t[3]
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()