1
+ import heapq
2
+
3
+ class Solution :
4
+ """
5
+ This solution is implemented with a Dynamic Programming approach + bottom up:
6
+ n-th ugly number = min(n previous ugly numbers)
7
+
8
+ Ugly numbers are stored in 3 lists: ugly_nbrs_2, ugly_nbrs_3, ugly_nbrs_5
9
+ A list i contains all ugly numbers computed by multiplying a previous ugly number by i
10
+
11
+ the min is computed by the min of the 1st unseen number from each list
12
+
13
+ """
14
+
15
+ # Time Complexity: O(n)
16
+ # Space Complexity: O(n)
17
+ def nthUglyNumber (self , n : int ) -> int :
18
+
19
+ if n == 0 :
20
+ return n
21
+
22
+ ugly_nbrs_2 = [2 ]
23
+ ugly_nbrs_3 = [3 ]
24
+ ugly_nbrs_5 = [5 ]
25
+
26
+ # Indexes to keep track of unseen numbers
27
+ idx_2 = 0
28
+ idx_3 = 0
29
+ idx_5 = 0
30
+
31
+ # prev_ugly_nbr is to skip duplicate values
32
+ ugly_nbr = 1
33
+ prev_ugly_nbr = 1
34
+ while n > 1 :
35
+
36
+ ugly_nbr = min (ugly_nbrs_2 [idx_2 ], ugly_nbrs_3 [idx_3 ], ugly_nbrs_5 [idx_5 ])
37
+ if ugly_nbr == ugly_nbrs_2 [idx_2 ]:
38
+ idx_2 += 1
39
+
40
+ elif ugly_nbr == ugly_nbrs_3 [idx_3 ]:
41
+ idx_3 += 1
42
+
43
+ else :
44
+ idx_5 += 1
45
+
46
+ if ugly_nbr == prev_ugly_nbr :
47
+ continue
48
+
49
+ ugly_nbrs_2 .append (ugly_nbr * 2 )
50
+ ugly_nbrs_3 .append (ugly_nbr * 3 )
51
+ ugly_nbrs_5 .append (ugly_nbr * 5 )
52
+
53
+ n -= 1
54
+ prev_ugly_nbr = ugly_nbr
55
+
56
+ return ugly_nbr
57
+
58
+ class SolutionHeapQ :
59
+ """
60
+ The solution is implemented with a Dynamic Programming approach + bottom up:
61
+ n-th ugly number = min(n previous ugly numbers)
62
+
63
+ The min operation is implemented with a min-heap
64
+
65
+ The issue in this solution is min operation running time: O(logn)
66
+ See time analysis below
67
+
68
+ """
69
+ # Time Complexity: T(n) = Sum(i in [1:n]) T(pop) + 3 T(push)
70
+ # T(n) = (0 + 0 + 0 + log2) + (log3 + log2 + log3 + log4) + ... + log2i-1 + log(2i - 2) + log(2i - 1) + log(2i) + ...
71
+ # T(n) < Sum(i in [1:n]) 4 log2i
72
+ # T(n) < 4 log(2 * 4 * ... * 2i * ... 2n) < 4 log(2**n * 1 * 2 * ... * i * ... * n)
73
+ # T(n) < 4n + 4 logn!
74
+ # T(n) = O(n + logn!)
75
+ # Space Complexity: S(n) = (3 - 1) + (3 - 1) + ... + (3 - 1) = O(n)
76
+ # At each iteration, we push 3 items and we pop 1 item.
77
+ def nth_ugly_number (self , n : int ) -> int :
78
+
79
+ if n == 0 :
80
+ return n
81
+
82
+ ugly_nbrs = []
83
+ heapq .heappush (ugly_nbrs , 1 )
84
+
85
+ ugly_nbr = - 1
86
+ prev_ugly_nbr = - 1
87
+ while n > 0 :
88
+
89
+ ugly_nbr = heapq .heappop (ugly_nbrs )
90
+ if ugly_nbr == prev_ugly_nbr :
91
+ continue
92
+
93
+ heapq .heappush (ugly_nbrs , ugly_nbr * 2 )
94
+ heapq .heappush (ugly_nbrs , ugly_nbr * 3 )
95
+ heapq .heappush (ugly_nbrs , ugly_nbr * 5 )
96
+
97
+ n -= 1
98
+ prev_ugly_nbr = ugly_nbr
99
+
100
+ return ugly_nbr
101
+
102
+ class SolutionBFS :
103
+ """
104
+ Ugly numbers could be seen as a 3-ary tree which the number 1 is the root
105
+ 1
106
+ / | \
107
+ 2 3 5
108
+ / / | /|\ \ \ \
109
+ 4 6 10 6 9 15 10 15 25
110
+ /|
111
+ 8 12
112
+ This 3-ary tree could be traversed with a BFS approach
113
+ For all node of a level (i), we compute the node of level (i + 1)
114
+
115
+ Even though, nodes values is increasing, they aren't sorted neither inside levels nor between levels
116
+
117
+ The challenge is therefore how to find the nth ugly number.
118
+
119
+ My solution is to store all ugly numbers sorted.
120
+ I stop when I reach a length of n and the last item is less than the min ugly number visited in the current level
121
+
122
+ It's not efficient!
123
+ I shouldn't split nodes by level.
124
+ I should have all tree nodes together and get the next value as the min of all node.
125
+ Solution: Min Heap.
126
+
127
+ """
128
+ def nth_ugly_number (self , n : int ) -> int :
129
+
130
+ if n <= 1 :
131
+ return n
132
+
133
+ n_ugly_nbrs = [1 ]
134
+ curr_level = [1 ]
135
+ while n > 0 :
136
+
137
+ next_level = []
138
+ curr_min_ugly = math .inf
139
+ for ugly_n in curr_level :
140
+
141
+ next_ugly = ugly_n * 2
142
+ if not next_ugly in next_level :
143
+ next_level .append (next_ugly )
144
+ curr_min_ugly = min (curr_min_ugly , next_ugly )
145
+
146
+ next_ugly = ugly_n * 3
147
+ if not next_ugly in next_level :
148
+ next_level .append (next_ugly )
149
+ curr_min_ugly = min (curr_min_ugly , next_ugly )
150
+
151
+ next_ugly = ugly_n * 5
152
+ if not next_ugly in next_level :
153
+ next_level .append (next_ugly )
154
+ curr_min_ugly = min (curr_min_ugly , next_ugly )
155
+
156
+ curr_level = next_level
157
+ n_ugly_nbrs .extend (next_level )
158
+ n_ugly_nbrs .sort ()
159
+ if len (n_ugly_nbrs ) >= n and n_ugly_nbrs [n - 1 ] <= curr_min_ugly :
160
+ break
161
+
162
+ return n_ugly_nbrs [n - 1 ]
163
+
164
+
0 commit comments