We start by initializing the min-heap with the first ugly number 1 and a set to track numbers already added to avoid duplicates.
💡 This initialization sets the foundation for generating ugly numbers in ascending order without repetition.
Line:heap = [1]
seen = {1}
ugly = 1
💡 The heap starts with 1, the smallest ugly number, ensuring the algorithm begins correctly.
traverse
Pop smallest ugly number (iteration 1)
Pop the smallest number from the heap, which is 1, marking the first ugly number found.
💡 Popping the smallest number ensures we process ugly numbers in ascending order.
Line:ugly = heapq.heappop(heap)
💡 The first ugly number is 1, confirming the algorithm's correctness at the start.
insert
Insert multiples of 1: multiply by 2, 3, and 5
Calculate 1 * 2 = 2, 1 * 3 = 3, and 1 * 5 = 5, check if each is new, then add them to the heap and seen set.
💡 Inserting multiples expands the heap with new candidate ugly numbers efficiently in one step.
Line:for factor in [2,3,5]:
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
💡 Multiplying by 2, 3, and 5 generates the next possible ugly numbers 2, 3, and 5.
traverse
Pop smallest ugly number (iteration 2)
Pop the smallest number from the heap, which is 2, marking the second ugly number found.
💡 Extracting the smallest ensures the sequence is in ascending order.
Line:ugly = heapq.heappop(heap)
💡 The second ugly number is 2, confirming the heap order.
insert
Insert multiples of 2: multiply by 2, 3, and 5
Calculate 2 * 2 = 4, 2 * 3 = 6, and 2 * 5 = 10, check if each is new, then add them to the heap and seen set.
💡 Multiplying by 2, 3, and 5 generates new candidates from the current ugly number efficiently in one step.
Line:for factor in [2,3,5]:
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
💡 These are the next candidates generated from 2.
traverse
Pop smallest ugly number (iteration 3)
Pop the smallest number from the heap, which is 3, marking the third ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 3 is the next ugly number in sequence.
insert
Insert multiples of 3: multiply by 2, 3, and 5
Calculate 3 * 2 = 6, 3 * 3 = 9, and 3 * 5 = 15, check if each is new, then add them to the heap and seen set, skipping duplicates.
💡 Avoiding duplicates keeps the heap efficient and correct while adding new candidates.
Line:for factor in [2,3,5]:
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
💡 Duplicates are ignored; new candidates 9 and 15 are added.
traverse
Pop smallest ugly number (iteration 4)
Pop the smallest number from the heap, which is 4, marking the fourth ugly number found.
💡 Continuing to extract the smallest number maintains ascending order.
Line:ugly = heapq.heappop(heap)
💡 4 is the next ugly number in the sequence.
insert
Insert multiples of 4: multiply by 2, 3, and 5
Calculate 4 * 2 = 8, 4 * 3 = 12, and 4 * 5 = 20, check if each is new, then add them to the heap and seen set.
💡 Multiplying by 2, 3, and 5 adds new candidates from current ugly number efficiently.
Line:for factor in [2,3,5]:
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
💡 These are new candidates generated from 4.
traverse
Pop smallest ugly number (iteration 5)
Pop the smallest number from the heap, which is 5, marking the fifth ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 5 is the next ugly number in the sequence.
insert
Insert multiples of 5: multiply by 2, 3, and 5 with duplicate checks
Calculate 5 * 2 = 10, 5 * 3 = 15, and 5 * 5 = 25, insert only new candidates into the heap and seen set.
💡 Avoiding duplicates keeps the heap efficient and correct while adding new candidates.
Line:for factor in [2,3,5]:
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
💡 Duplicates are ignored; new candidate 25 is added.
traverse
Pop smallest ugly number (iteration 6)
Pop the smallest number from the heap, which is 6, marking the sixth ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 6 is the next ugly number in the sequence.
traverse
Pop smallest ugly number (iteration 7)
Pop the smallest number from the heap, which is 8, marking the seventh ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 8 is the next ugly number in the sequence.
traverse
Pop smallest ugly number (iteration 8)
Pop the smallest number from the heap, which is 9, marking the eighth ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 9 is the next ugly number in the sequence.
traverse
Pop smallest ugly number (iteration 9)
Pop the smallest number from the heap, which is 10, marking the ninth ugly number found.
💡 Extracting the smallest number continues the ascending sequence.
Line:ugly = heapq.heappop(heap)
💡 10 is the next ugly number in the sequence.
traverse
Pop smallest ugly number (iteration 10)
Pop the smallest number from the heap, which is 12, marking the tenth ugly number found and the final answer.
💡 Extracting the nth ugly number completes the algorithm.
Line:ugly = heapq.heappop(heap)
💡 12 is the 10th ugly number, the final answer.
import heapq
def nthUglyNumber(n):
heap = [1] # STEP 1
seen = {1} # STEP 1
ugly = 1 # STEP 1
for i in range(n): # STEP 2-16
ugly = heapq.heappop(heap) # STEP 2,4,6,...,16
for factor in [2, 3, 5]: # STEP 3,5,7,...
new_ugly = ugly * factor
if new_ugly not in seen:
seen.add(new_ugly)
heapq.heappush(heap, new_ugly)
return ugly
if __name__ == '__main__':
print(nthUglyNumber(10)) # Output: 12
📊
Ugly Number II (Smooth Numbers) - Watch the Algorithm Execute, Step by Step
Watching this step-by-step heap operation reveals how the algorithm efficiently generates ugly numbers without redundant calculations.
Step 1/16
·Active fill★Answer cell
setup
i
1
0
move_left
Result: 1
insert
2
0
i
3
1
5
2
Result: 1
move_left
3
0
5
1
Result: 2
insert
3
0
5
1
i
4
2
6
3
10
4
Result: 2
move_left
4
0
5
1
10
2
i
6
3
Result: 3
insert
4
0
5
1
10
2
i
6
3
9
4
15
5
Result: 3
move_left
5
0
6
1
10
2
15
3
i
9
4
Result: 4
insert
5
0
6
1
10
2
15
3
i
9
4
8
5
12
6
20
7
Result: 4
move_left
6
0
8
1
10
2
15
3
9
4
i
20
5
12
6
Result: 5
insert
6
0
8
1
10
2
15
3
9
4
i
20
5
12
6
25
7
Result: 5
move_left
8
0
9
1
10
2
15
3
25
4
20
5
i
12
6
Result: 6
move_left
9
0
12
1
10
2
15
3
25
4
20
5
Result: 8
move_left
10
0
12
1
20
2
15
3
25
4
Result: 9
move_left
12
0
15
1
20
2
25
3
Result: 10
move_left
15
0
25
1
20
2
Result: 12
Key Takeaways
✓ The min-heap ensures ugly numbers are generated in ascending order without missing any.
This ordering is hard to see from code alone but is clear when watching the heap state change step-by-step.
✓ The seen set prevents duplicate entries, keeping the heap efficient and the algorithm correct.
Without this, duplicates would cause redundant work and incorrect results, which is easier to understand visually.
✓ Each popped ugly number generates new candidates by multiplying by 2, 3, and 5, expanding the sequence systematically.
Watching these insertions after each pop clarifies the dependency and growth of the ugly number sequence.
Practice
(1/5)
1. You need to count how many prime numbers are less than a given integer n. Which algorithmic approach guarantees the most efficient solution for this problem?
easy
A. Check each number from 2 to n-1 for primality by testing divisibility up to n-1.
B. Use a sieve method that iteratively marks multiples of primes starting from 2 up to sqrt(n).
C. Apply a dynamic programming approach that builds prime counts based on previous counts.
D. Use a greedy approach that picks primes by checking divisibility only by the last found prime.
Solution
Step 1: Understand the problem
We want to count primes less than n efficiently, which requires avoiding repeated primality checks.
Step 2: Identify the optimal approach
The sieve method marks multiples of primes starting from 2 up to sqrt(n), efficiently eliminating non-primes in O(n log log n) time.
Final Answer:
Option B -> Option B
Quick Check:
Sieve is the classic and optimal approach for counting primes [OK]
Hint: Sieve marks multiples up to sqrt(n) for efficiency [OK]
Common Mistakes:
Thinking brute force primality check is efficient enough
Confusing DP with sieve
Assuming greedy divisibility checks suffice
2. Given the following Python code, what is the value of result after processing the input "AB"?
easy
A. 28
B. 27
C. 52
D. 26
Solution
Step 1: Compute powers array for "AB"
Length n=2, powers initialized as [1,1]. Loop sets powers[0] = powers[1]*26 = 1*26=26, so powers = [26,1].
Step 2: Calculate result by summing value * power
For 'A': value=1, result += 1*26=26; For 'B': value=2, result += 2*1=2; total result=28.
3. Consider the following buggy code snippet for converting an Excel column title to a number. Which line contains the subtle bug that causes incorrect results for inputs like "A" or "Z"?
medium
A. Line 4: powers computation loop
B. Line 7: initialization of result
C. Line 9: calculation of value using zero-based indexing
D. Line 10: accumulation of result using powers
Solution
Step 1: Identify how letter values are computed
The code uses ord(ch) - ord('A'), which makes 'A' = 0 instead of 1.
Step 2: Understand impact on result
This causes all results to be off by one, e.g., "A" returns 0 instead of 1.
Final Answer:
Option C -> Option C
Quick Check:
Correct letter values must be 1-based, not zero-based [OK]
Hint: Check if 'A' maps to 1, not 0 [OK]
Common Mistakes:
Using zero-based indexing for letters
Miscomputing powers array
Incorrect accumulation order
4. The following code attempts to determine if a number is happy but contains a subtle bug. Identify the line with the bug.
def isHappy(n: int) -> bool:
def digit_square_sum(num: int) -> int:
total = 0
while num > 0:
digit = num % 10
total += digit * digit
num //= 10
return total
while n != 1:
n = digit_square_sum(n)
return True
medium
A. Line with 'while n != 1:' loop condition
B. Missing cycle detection causing infinite loop
C. Line with 'return True' after the loop
D. Line with 'n = digit_square_sum(n)' inside the loop
Solution
Step 1: Analyze loop termination condition
The loop only terminates when n == 1, but if n falls into an unhappy cycle, it never reaches 1.
Step 2: Identify missing cycle detection
Without tracking seen numbers or using a known cycle set, the code loops infinitely for unhappy numbers.
Final Answer:
Option B -> Option B
Quick Check:
Infinite loop occurs due to missing cycle detection [OK]
Hint: No cycle detection -> infinite loop on unhappy numbers [OK]
Common Mistakes:
Assuming loop condition suffices
Ignoring cycle detection necessity
Misreading return placement
5. Suppose the clock can show times with fractional minutes (e.g., 3:30.5). Which modification to the precomputation approach is necessary to handle queries with fractional minutes correctly?
hard
A. Increase precomputed table size to include fractional minute indices (e.g., 60 * 10 for tenths of minutes).
B. Precompute only hour angles and calculate minute angles on the fly for fractional minutes.
C. Round fractional minutes down to nearest integer and use existing precomputed table.
D. Use the brute force direct calculation formula at query time instead of precomputation.
Solution
Step 1: Understand fractional minute impact
Precomputing all fractional minute values is infeasible due to infinite possibilities.
Step 2: Evaluate options
Increasing table size exponentially is impractical; rounding loses precision; precomputing only hour angles is insufficient because minute angle varies continuously.
Step 3: Use direct calculation
Calculating angles on the fly with the formula handles fractional minutes accurately and efficiently.
Final Answer:
Option D -> Option D
Quick Check:
Direct formula supports fractional inputs without huge memory [OK]
Hint: Precompute only works for discrete inputs; fractional requires formula [OK]
Common Mistakes:
Trying to precompute infinite fractional values
Rounding fractional minutes losing accuracy
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.