💡 Heap is updated dynamically to reflect leftover counts.
insert
Update previous element to current popped
Update the previous element to the current popped element with incremented count.
💡 Tracking leftover counts is crucial for correctness.
Line:prev_count, prev_num = count + 1, num
💡 Previous element pointer moves forward with updated count.
traverse
Pop last element and append to result
Pop the last element from the heap and append it to the result array, completing the rearrangement.
💡 Appending the last element finishes the array without adjacent duplicates.
Line:count, num = heapq.heappop(heap)
res.append(num)
💡 The final element is placed ensuring no two adjacent elements are equal.
from collections import Counter
import heapq
def make_array_beautiful_heap(arr):
freq = Counter(arr) # STEP 1
max_freq = max(freq.values()) # STEP 2
n = len(arr)
if max_freq > (n + 1) // 2:
return [] # STEP 2 decision
heap = [(-count, num) for num, count in freq.items()] # STEP 3
heapq.heapify(heap) # STEP 3
res = []
prev_count, prev_num = 0, None
while heap:
count, num = heapq.heappop(heap) # STEP 4,8,12,16,20
res.append(num) # STEP 5,9,13,17,20
if prev_count < 0:
heapq.heappush(heap, (prev_count, prev_num)) # STEP 6,10,14,18
prev_count, prev_num = count + 1, num # STEP 7,11,15,19
return res
if __name__ == '__main__':
arr = [1,1,2,2,3,3]
print(make_array_beautiful_heap(arr))
📊
Make the Array Beautiful (Sort + Greedy) - Watch the Algorithm Execute, Step by Step
Watching each step reveals how the heap structure and greedy choices prevent adjacent duplicates, which is hard to grasp from code alone.
Step 1/20
·Active fill★Answer cell
record
1
0
1
1
2
2
2
3
3
4
3
5
Result: {}
compare
1
0
1
1
2
2
2
3
3
4
3
5
Result: {}
expand
heap_top
1
0
1
1
2
2
2
3
3
4
3
5
Result: []
move_left
Result: []
record
res_end
1
0
Result: [1]
prune
1
0
Result: [1]
record
prev
1
0
Result: [1]
move_left
heap_top
1
0
Result: [1]
record
1
0
res_end
2
1
Result: [1,2]
expand
heap_top
1
0
2
1
Result: [1,2]
record
1
0
prev
2
1
Result: [1,2]
move_left
heap_top
1
0
2
1
Result: [1,2]
record
1
0
2
1
res_end
1
2
Result: [1,2,1]
expand
heap_top
1
0
2
1
1
2
Result: [1,2,1]
record
1
0
2
1
prev
1
2
Result: [1,2,1]
move_left
heap_top
1
0
2
1
1
2
Result: [1,2,1]
record
1
0
2
1
1
2
res_end
3
3
Result: [1,2,1,3]
expand
heap_top
1
0
2
1
1
2
3
3
Result: [1,2,1,3]
record
1
0
2
1
1
2
prev
3
3
Result: [1,2,1,3]
record
1
0
2
1
1
2
3
3
2
4
res_end
3
5
Result: [1,2,1,3,2,3]
Key Takeaways
✓ Using a max heap to always pick the two most frequent elements ensures no two adjacent elements are the same.
This insight is hard to see from code alone because the heap operations and greedy logic are intertwined.
✓ Tracking the previously used element and pushing it back only if it still has remaining count prevents immediate repetition.
This mechanism is subtle and watching the pointer updates clarifies how adjacency is avoided.
✓ The frequency check at the start quickly determines if a valid arrangement is possible, saving unnecessary computation.
Understanding this early pruning is easier when you see the frequency counts and max frequency visually.
Practice
(1/5)
1. Consider the following Python function that finds the minimum time difference between time points. What is the returned value when the input is ["01:01", "02:01", "01:02"]?
def findMinDifference(timePoints):
seen = [False] * 1440
for t in timePoints:
h, m = map(int, t.split(':'))
total = h * 60 + m
if seen[total]:
return 0
seen[total] = True
prev = -1
first = -1
min_diff = 1440
for i in range(1440):
if seen[i]:
if prev != -1:
min_diff = min(min_diff, i - prev)
else:
first = i
prev = i
min_diff = min(min_diff, 1440 - prev + first)
return min_diff
easy
A. 59
B. 0
C. 60
D. 1
Solution
Step 1: Convert times to minutes and mark seen
"01:01" -> 61, "02:01" -> 121, "01:02" -> 62; all unique, so no early return 0.
Step 2: Iterate over seen array to find minimum adjacent difference
Adjacent times are 61 and 62 (diff 1), 62 and 121 (diff 59). Minimum difference is 1.
Final Answer:
Option D -> Option D
Quick Check:
Minimum adjacent difference found is 1 minute [OK]
Hint: Check adjacent differences after sorting times [OK]
Common Mistakes:
Confusing difference between 61 and 121 as minimum
Forgetting to check circular difference
Returning 0 incorrectly when no duplicates
2. Consider the following Python code implementing the Dutch National Flag algorithm:
nums[mid]=2 triggers swap with nums[high]=1, array becomes [1,0,2], high=1, mid=0.
Step 2: Trace second iteration with mid=0, high=1
nums[mid]=1, mid increments to 1; nums[mid]=0 triggers swap with nums[low]=1, array becomes [0,1,2], low=1, mid=2.
Final Answer:
Option C -> Option C
Quick Check:
Array sorted as [0,1,2] after one pass [OK]
Hint: Swapping 2 with high pointer reduces problem size [OK]
Common Mistakes:
Not updating mid after swapping 0
Incrementing mid after swapping 2 (should not)
Confusing low and mid pointers
3. You are given a matrix and asked to sort each diagonal (from top-left to bottom-right) independently in ascending order. Which algorithmic approach best guarantees an efficient and correct solution?
easy
A. Use a greedy approach to swap elements along diagonals until sorted.
B. Sort the entire matrix row-wise and column-wise repeatedly until diagonals are sorted.
C. Group elements by their diagonal key (row - column), sort each group, then reassign back.
D. Apply dynamic programming to find the longest sorted subsequence along diagonals.
Solution
Step 1: Identify diagonal grouping key
Each diagonal can be uniquely identified by the difference between its row and column indices (row - col). Grouping elements by this key allows independent sorting.
Step 2: Sort and reassign elements per diagonal group
Sorting each group separately and then placing elements back ensures diagonals are sorted without affecting others.
Final Answer:
Option C -> Option C
Quick Check:
Grouping by row - col and sorting each group sorts diagonals correctly [OK]
Hint: Group by row - col to isolate diagonals [OK]
Common Mistakes:
Trying to sort entire matrix repeatedly
Using greedy swaps that don't guarantee sorted diagonals
Applying DP unrelated to sorting diagonals
4. Given the following code snippet implementing the optimal wiggle sort, what is the content of the array nums after the while loop completes when the input is [1, 3, 2]?
Virtual indices for i=0,1,2 are (1+2*0)%3=1, (1+2*1)%3=0, (1+2*2)%3=2.
- i=0: nums[1]=2 equals median -> i=1
- i=1: nums[0]=1 less than median -> swap with nums[virtual_index(right=2)]=nums[2]=3, right=1
- After swap: nums=[3,2,1], i=1, right=1
- i=1: nums[0]=3 greater than median -> swap with nums[virtual_index(left=0)]=nums[1]=2, left=1, i=2
- Final nums=[2,3,1]