Heap / Priority Queue
Master this topic with zero to advance depth.
Heap and Priority Queue Mental Models
A Heap is a specialized tree-based data structure that satisfies the heap property. A Priority Queue is an abstract data type that works like a regular queue but where each element has a "priority" associated with it.
1. Types of Heaps
- Max-Heap: The value of the root node must be the greatest among all its children. The same property must be recursively true for all subtrees.
- Min-Heap: The value of the root node must be the smallest among all its children.
2. Binary Heap Structure (Array Representation)
A binary heap is typically represented as an array. For an index i:
- Parent:
(i - 1) / 2 - Left Child:
2 * i + 1 - Right Child:
2 * i + 2
3. Core Operations (Time Complexity)
| Operation | Description | Time Complexity |
|---|---|---|
| Insert | Add an element and Sift-Up | |
| Extract Min/Max | Remove root and Sift-Down | |
| Peek | View root value | |
| Heapify | Create heap from array |
4. Visualizing Sift-Up (Insertion)
Initial Max-Heap: [10, 8, 5]
Insert 15:
1. [10, 8, 5, 15] (Add at end)
2. [10, 15, 5, 8] (Swap 15 with 8, 15 > 8)
3. [15, 10, 5, 8] (Swap 15 with 10, 15 > 10)
Done: Root is 15.5. Common Use Cases
- Top K Elements: Use a heap of size K ().
- Merging Sorted Streams: K-way merge ().
- Dijkstra's / Prim's Algorithms: Finding shortest paths/MST.
Top K Frequent Elements
Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.
Visual Representation
nums = [1, 1, 1, 2, 2, 3], k = 2
Frequency Count Buckets:
0: []
1: [3] (val 3 appears 1 time)
2: [2] (val 2 appears 2 times)
3: [1] (val 1 appears 3 times)
4: []
5: []
6: []
Iterate buckets from right to left:
Bucket 3 -> [1]
Bucket 2 -> [2]
Result (k=2): [1, 2]Examples
1 appears 3 times, 2 appears 2 times, 3 appears 1 time. Top 2 are [1, 2].
Level I: Sorting (Naive)
Intuition
Count the frequency of each element and then sort the elements based on their count in descending order. Pick the first k elements.
Thought Process
- Use a Hash Map to count occurrences.
- Create a list of pairs
(element, frequency). - Sort the list by frequency in descending order.
- Take the top
kelements.
Detailed Dry Run
nums = [1, 1, 2], k = 1
- Map: {1: 2, 2: 1}
- Pairs: [(1, 2), (2, 1)]
- Sorted: [(1, 2), (2, 1)]
- result: [1]
Level II: Bucket Sort (Optimal)
Intuition
Since frequency is bounded by array length , we can use buckets where bucket[i] stores elements that appear times. Iterate from down to 1.
Thought Process
- Count frequencies with a Map.
- Create an array of lists
bucketsof size . - Place each element in its corresponding frequency bucket.
- Traverse buckets from right to left to collect top elements.
Detailed Dry Run
nums = [1, 1, 1, 2, 2, 3], k = 2
- Map: {1:3, 2:2, 3:1}
- Buckets: [[], [3], [2], [1], [], [], []]
- Bucket 3: adds [1]. Result [1]
- Bucket 2: adds [2]. Result [1, 2]. Done.
Level III: Min-Heap (Optimal)
Intuition
Instead of sorting all elements, we can maintain a Min-Heap of size k. If the size exceeds k, we pop the element with the smallest frequency. At the end, the heap contains the k most frequent elements.
Thought Process
- Count frequencies using a Map.
- Iterate through unique elements and push them into a Min-Heap based on frequency.
- If heap size >
k, pop from heap. - The remaining elements in the heap are the result.
Detailed Dry Run
nums = [1, 1, 1, 2, 2, 3], k = 2
- Map: {1:3, 2:2, 3:1}
- Push 1 (freq 3): Heap [(1,3)]
- Push 2 (freq 2): Heap [(2,2), (1,3)]
- Push 3 (freq 1): Heap [(3,1), (2,2), (1,3)] -> Size 3 > k, Pop (3,1). Final Heap: [(2,2), (1,3)] -> Result [1, 2]
Kth Largest Element in an Array
Given an integer array nums and an integer k, return the kth largest element in the array.
Note that it is the kth largest element in the sorted order, not the kth distinct element.
Visual Representation
nums = [3,2,1,5,6,4], k = 2
Min-Heap (size k=2):
[1] -> [1, 2] -> [2, 3] -> [3, 5] -> [5, 6] -> [5, 6] (after 4)
Heap top is 5th largest.
Quickselect (Partitions):
[3, 2, 1, 5, 6, 4] -> pivot 4
[3, 2, 1] | [4] | [5, 6]
Target index is in the right partition.Examples
Level I: Sorting (Naive)
Intuition
The simplest way is to sort the entire array in descending order and return the element at index k-1.
Thought Process
- Sort the input array.
- Access the element at
length - k(for ascending sort) ork - 1(for descending sort).
Detailed Dry Run
nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
- Sorted: [1, 2, 2, 3, 3, 4, 5, 5, 6]
- Index (9 - 4) = 5
- nums[5] = 4
Level II: Quickselect (Average Case Optimal)
Intuition
Based on the Partition logic of Quicksort. We only care about the partition that contains the th largest element (index ).
Thought Process
- Choose a pivot element.
- Partition the array around the pivot like in Quicksort.
- Compare the pivot's final index with target index .
- Recurse into the sub-array containing the target index.
Detailed Dry Run
nums = [3, 2, 1, 5, 6, 4], k = 2, target = 6-2 = 4
- Partition: [3, 2, 1, 4, 6, 5], 4 is at index 3.
- 3 < 4: Recurse [6, 5]
- Partition: [5, 6], 6 is at index 5.
- 5 > 4: Recurse [5]
- Index 4 is 5. Return 5.
Level III: Min-Heap (Optimal)
Intuition
Maintain a Min-Heap of size k. As we iterate through the array, if we find an element larger than the heap's minimum, we replace the minimum with it. After one pass, the heap contains the k largest elements, and the top is the kth largest.
Thought Process
- Initialize a Min-Heap.
- For each number in
nums:- Push to heap.
- If heap size >
k, pop the smallest element.
- The root of the heap is our answer.
Detailed Dry Run
nums = [3, 2, 1, 5, 6, 4], k = 2
- Push 3: Heap [3]
- Push 2: Heap [2, 3]
- Push 1: Heap [1, 2, 3] -> Pop 1. Heap [2, 3]
- Push 5: Heap [2, 3, 5] -> Pop 2. Heap [3, 5]
- Push 6: Heap [3, 5, 6] -> Pop 3. Heap [5, 6]
- Push 4: Heap [4, 5, 6] -> Pop 4. Heap [5, 6] Result: Top is 5
Task Scheduler
Given a characters array tasks, representing the tasks a CPU needs to do, where each letter represents a different task. Tasks can be done in any order. Each task is done in one unit of time. For each unit of time, the CPU could complete either one task or just be idle.
However, there is a non-negative integer n that represents the cooldown period between two same tasks (the same letter in the array), that is that there must be at least n units of time between any two same tasks.
Return the least number of units of time that the CPU will take to finish all the given tasks.
Visual Representation
tasks = [A, A, A, B, B, B], n = 2
Simulation Steps:
1. A (freq 3->2) | Waitlist: [(A, T=3)]
2. B (freq 3->2) | Waitlist: [(A, T=3), (B, T=4)]
3. IDLE | Waitlist: [(A, T=3), (B, T=4)]
4. A (freq 2->1) | A returns from waitlist at T=3
...
Result: A B IDLE A B IDLE A B = 8 unitsExamples
Level I: Max-Heap + Cooling Queue (Simulation)
Intuition
We want to process the most frequent tasks first to minimize idle time. Use a Max-Heap to keep track of task frequencies and a Queue to manage tasks in their cooldown period.
Thought Process
- Count frequency of each task.
- Push frequencies into a Max-Heap.
- While heap or cooldown queue is not empty:
- Increment time.
- If heap has tasks, take the most frequent, decrement its count, and if count > 0, add to queue with its 'available time'.
- If queue has a task whose cooldown is over, push it back to the heap.
Detailed Dry Run
tasks = [A,A,A,B,B,B], n = 2
- Heap: [A:3, B:3], Queue: [], Time: 0
- Time 1: Process A. Heap [B:3], Queue [(A:2, T=4)]
- Time 2: Process B. Heap [], Queue [(A:2, T=4), (B:2, T=5)]
- Time 3: Idle. Queue is cool until T=4.
- Time 4: Q.pop A. Heap [A:2]... and so on until 8.
Level II: Greedy (Simplified Priority Queue)
Intuition
Always process the task with the highest remaining frequency. We use a single loop to calculate the size of chunks of size .
Thought Process
- Count frequencies.
- Use a Priority Queue (Max-Heap).
- In each chunk of time units, pick tasks from heap.
- If heap is empty but we still have tasks to process (temporarily removed), add 'idle' counts.
Detailed Dry Run
tasks = [A, A, A, B, B, B], n = 2
- Frequencies: {A:3, B:3}
- Iteration 1: Chunk size 3. Pick A (rem 2), B (rem 2). Total time 2. Remaining tasks exist, so add 1 idle. Time 3.
- Iteration 2: Pick A (rem 1), B (rem 1). Time 6.
- Iteration 3: Pick A (rem 0), B (rem 0). Time 8. Done.
Level III: Greedy (Mathematical)
Intuition
The total time is determined by the task with the maximum frequency. Let the maximum frequency be maxFreq. There are maxFreq - 1 gaps between these tasks, each of size n. We fill these gaps with other tasks. If the total units required is less than the total number of tasks, the answer is the total tasks.
Formula
partCount = maxFreq - 1
partLength = n - (countOfTasksWithMaxFreq - 1)
emptySlots = partCount * partLength
availableTasks = tasks.length - maxFreq * countOfTasksWithMaxFreq
idles = max(0, emptySlots - availableTasks)
Result = tasks.length + idles
Detailed Dry Run
tasks = [A,A,A,B,B,B], n = 2
- maxFreq = 3 (A and B both have 3).
- Max freq task count = 2 (A and B).
- Formula: (3-1) * (2+1) + 2 = 8. Total Time = 8.
K Closest Points to Origin
Given an array of points where points[i] = [xi, yi] represents a point on the X-Y plane and an integer k, return the k closest points to the origin (0, 0).
The distance between two points on the X-Y plane is the Euclidean distance .
You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in).
Visual Representation
points = [[1,3],[-2,2]], k = 1
Distance of [1,3] = sqrt(1^2 + 3^2) = sqrt(10) ≈ 3.16
Distance of [-2,2] = sqrt((-2)^2 + 2^2) = sqrt(8) ≈ 2.83
Closest Point: [-2, 2]Examples
Level I: Sorting (Naive)
Intuition
Calculate the squared distance for every point, then sort all points based on these distances. Return the first k points.
Thought Process
- Use a custom comparator to compare points and based on vs .
- Sort the array using this comparator.
- Return the sub-array of size
k.
Detailed Dry Run
points = [[1,3], [-2,2]], k = 1
- Dist sq: [10, 8]
- Sorted by dist: [[-2,2], [1,3]]
- Take first k=1: [[-2,2]]
Level II: Quickselect (Average Case Optimal)
Intuition
Similar to Kth Largest Element, we can use the Partition logic to find the closest points in average time.
Thought Process
- Define distance function .
- Partition the points based on .
- If pivot index equals , all points to the left are the closest.
- Recurse into the necessary partition.
Detailed Dry Run
points = [[3,3], [5,-1], [-2,4]], k = 2 Distances: [18, 26, 20]
- Partition around pivot 20: [[3,3], [-2,4], [5,-1]]. Pivot 20 is at index 1.
- k=2, so we need to ensure index 1 is part of the result. Return points[0...1].
Level III: Max-Heap (Optimal)
Intuition
Maintain a Max-Heap of size k to store the points with the smallest distances. If we find a point with a smaller distance than the maximum in our heap, we replace the maximum with it.
Thought Process
- Initialize a Max-Heap based on squared Euclidean distance.
- Iterate through all
points. - Push the current point into the heap.
- If heap size >
k, pop the point with the largest distance (the root). - After all points are processed, the heap contains the
kclosest points.
Detailed Dry Run
points = [[3,3],[5,-1],[-2,4]], k = 2
- Point [3,3], Dist 18: Heap [[3,3] (Dist 18)]
- Point [5,-1], Dist 26: Heap [[5,-1] (Dist 26), [3,3] (Dist 18)]
- Point [-2,4], Dist 20: Heap [[5,-1] (Dist 26), [-2,4], [3,3]] -> Pop [5,-1]. Final Heap: [[-2,4], [3,3]]
Meeting Rooms II
Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.
Visual Representation
intervals = [[0,30],[5,10],[15,20]]
Timeline:
0 5 10 15 20 30
|--|---|---|----|----|
[0 30] (Room 1)
[5 10] (Room 2)
[15 20] (Room 2 - reused)
Max overlapping at any time: 2Examples
Level I: Min-Heap (Interval Management)
Intuition
Sort the meetings by start time. Use a Min-Heap to track the end times of meetings currently in progress. If a new meeting starts after the earliest meeting ends (root of the heap), we can reuse that room.
Thought Process
- Sort
intervalsby start time. - Initialize a Min-Heap to store end times.
- For each interval:
- If heap is not empty and
heap.peek() <= current.start, pop the heap (room becomes free). - Push current interval's end time to heap.
- If heap is not empty and
- The final size of the heap is the number of rooms needed.
Detailed Dry Run
intervals = [[0,30],[5,10],[15,20]]
- Sorted: [[0,30],[5,10],[15,20]]
- Int [0,30]: Heap [30]
- Int [5,10]: Heap.peek(30) > 5. Push 10. Heap [10, 30]
- Int [15,20]: Heap.peek(10) <= 15. Pop 10. Push 20. Heap [20, 30] Result: Heap size = 2
Level II: Sweep Line (Difference Array Logic)
Intuition
Record all 'start' and 'end' events. A 'start' event increases the room count by 1, and an 'end' event decreases it by 1. The maximum room count at any point is our answer.
Thought Process
- For each interval , create two points: and .
- Sort todos points by time.
- If times are equal, process end events before start events if the interval is exclusive, OR start before end if inclusive. (LeetCode meetings are inclusive of start, exclusive of end, so end before start at same time).
- Traverse through sorted points and maintain a running sum.
Detailed Dry Run
intervals = [[0,30],[5,10],[15,20]]
- Events: (0,+1), (30,-1), (5,+1), (10,-1), (15,+1), (20,-1)
- Sorted: (0,+1), (5,+1), (10,-1), (15,+1), (20,-1), (30,-1)
- Sums: 1 -> 2 -> 1 -> 2 -> 1 -> 0 Max Sum: 2
Level III: Two Pointers (Chronological Ordering)
Intuition
Separate start times and end times and sort both. Iterate through the start times. If a start time is before the earliest end time, we need a room. If not, a room just became free, so we can move the end-time pointer.
Logic Steps
- Extract all
startsandendsinto separate arrays. - Sort both arrays.
- Use two pointers
sande. - If
starts[s] < ends[e], incrementusedRoomsands. - Else (room freed), increment
eands(total rooms stays same).
Detailed Dry Run
starts = [0, 5, 15], ends = [10, 20, 30]
- s=0, e=0: starts[0]=0 < ends[0]=10 -> rooms=1, s=1
- s=1, e=0: starts[1]=5 < ends[0]=10 -> rooms=2, s=2
- s=2, e=0: starts[2]=15 >= ends[0]=10 -> rooms=2, e=1, s=3 Result: 2 rooms.
Rearrange String k Distance Apart
Given a string s and an integer k, rearrange s such that the same characters are at least distance k from each other. If it is not possible to rearrange the string, return an empty string "".
Visual Representation
s = "aabbcc", k = 3
Frequency Count:
a: 2, b: 2, c: 2
Step-by-Step Selection (k=3):
Pos 0: Use 'a' (freq 2->1) | Wait: {a: wait 2}
Pos 1: Use 'b' (freq 2->1) | Wait: {a: wait 1, b: wait 2}
Pos 2: Use 'c' (freq 2->1) | Wait: {a: wait 0, b: wait 1, c: wait 2}
Pos 3: 'a' is free! Use 'a'| Wait: {b: wait 0, c: wait 1, a: wait 2}
...
Result: "abcabc"Examples
Level I: Greedy with Max-Heap (Standard)
Intuition
We should always try to place the most frequent characters as early as possible. However, once a character is used, it cannot be used again for the next k-1 positions. Use a Max-Heap to track frequencies and a queue to manage the 'waitlist'.
Thought Process
- Count frequency of each character.
- Push char-frequency pairs into a Max-Heap.
- While heap is not empty:
- Extract the character with the max frequency.
- Append it to the result.
- Decrement its frequency.
- Add it to a 'waitlist' queue.
- If the waitlist queue size reaches
k, pop the character from the front and push it back into the heap (if its freq > 0).
Detailed Dry Run
s = "aaabc", k = 3
- Map: {a:3, b:1, c:1}, Heap: [a:3, b:1, c:1], Wait: []
- Step 1: Use 'a'. Result: "a", Heap: [b:1, c:1], Wait: [(a, 2)]
- Step 2: Use 'b'. Result: "ab", Heap: [c:1], Wait: [(a, 2), (b, 0)]
- Step 3: Use 'c'. Result: "abc", Heap: [], Wait: [(a, 2), (b, 0), (c, 0)]
- Wait size 3: Push 'a' back to heap. Heap: [a:2]
- Continue... (If result length != s length, return "")
Level II: Greedy with Alphabet Array (O(N * 26))
Intuition
Instead of a Max-Heap, we can directly use a frequency array of size 26. At each position in the result string, we scan all 26 characters to find the one that: 1. Is allowed to be placed (distance since last use >= k). 2. Has the maximum remaining frequency.
Detailed Dry Run
s = "aabbcc", k = 3
- Freqs: {a:2, b:2, c:2}, LastPos: {a:-inf, b:-inf, c:-inf}
- Pos 0: 'a' is valid and max freq. Res: "a", Freqs: {a:1...}, LastPos: {a:0}
- Pos 1: 'a' invalid (1 < 3), 'b' is valid and max freq. Res: "ab"...
- Pos 3: 'a' becomes valid again (3-0 >= 3).
Level III: Optimal (Modified Greedy with Max-Freq Limit Check)
Intuition
This approach uses a Max-Heap but adds a check to see if it's mathematically possible to rearrange. If the most frequent character appears more times than the available slots given distance k, it's impossible. We use a waitlist (cooling queue) to manage the distance k requirement.
Detailed Dry Run
s = "aaabc", k = 3
- Freqs: {a:3, b:1, c:1}. Heap: [a:3, b:1, c:1]. Wait: []
- Pop 'a'. Res: "a". Freq: a:2. Wait: [a:2].
- Wait size < 3. Continue.
- If heap empty and res.length < s.length, return "".
Find K Pairs with Smallest Sums
You are given two integer arrays nums1 and nums2 sorted in non-decreasing order and an integer k.
Define a pair (u, v) which consists of one element from nums1 and one element from nums2.
Return the k pairs (u1, v1), (u2, v2), ..., (uk, vk) with the smallest sums.
Visual Representation
nums1 = [1, 7, 11], nums2 = [2, 4, 6], k = 3
Possible Pairs:
(1,2) sum=3
(1,4) sum=5
(1,6) sum=7
(7,2) sum=9 ...
Smallest 3 pairs: [[1,2], [1,4], [1,6]]Examples
Level I: Brute Force + Sorting
Intuition
Generate all possible pairs from the two arrays, calculate their sums, and sort them. Return the first k pairs.
Thought Process
- Nest two loops to iterate over
nums1andnums2. - Store each pair and its sum in a list.
- Sort the list by sum.
- Take the first
kelements.
Detailed Dry Run
nums1 = [1,2], nums2 = [3], k = 1
- Pairs: [[1,3] sum:4, [2,3] sum:5]
- Sorted: [[1,3], [2,3]]
- Result: [[1,3]]
Level II: Max-Heap of size K (Better Brute Force)
Intuition
Instead of storing all pairs and sorting, we can maintain a Max-Heap of size . If we find a pair smaller than the root of our Max-Heap, we replace the root. This reduces space complexity for large if is small.
Thought Process
- Use a Max-Heap to store
[u, v]pairs based on their sum. - Iterate through
nums1andnums2(limit to to avoid unnecessary checks). - Push each pair to the heap.
- If heap size exceeds , pop the largest (root).
- Return all pairs in the heap.
Detailed Dry Run
nums1 = [1, 2], nums2 = [3], k = 1
- Pair [1,3], sum 4. Heap: [[1,3]]
- Pair [2,3], sum 5. Heap: [[2,3], [1,3]] -> Pop [2,3]. Heap: [[1,3]] Result: [[1,3]]
Level III: Min-Heap (Optimal)
Intuition
Since arrays are sorted, the smallest pair is (nums1[0], nums2[0]). The next candidates are (nums1[1], nums2[0]) or (nums1[0], nums2[1]). We can use a Min-Heap to explore pairs efficiently.
Thought Process
- Push
(nums1[i], nums2[0], 0)for alli(or justi=0and expand both ways) into a Min-Heap. - In each step, pop the smallest pair
(nums1[i], nums2[j]). - Add it to result.
- Push the next potential pair
(nums1[i], nums2[j+1])into the heap. - Repeat
ktimes.
Detailed Dry Run
nums1 = [1, 7, 11], nums2 = [2, 4, 6], k = 3
- Heap: [(1,2,idx_in_nums2=0)]
- Pop (1,2). Result: [[1,2]]. Push next in nums2: (1,4,1). Heap: [(1,4,1)]
- Pop (1,4). Result: [[1,2],[1,4]]. Push next: (1,6,2). Heap: [(1,6,2), (7,2,0)] (Note: 7,2 is pushed later or initialized) Actually simpler: Push all nums1[i] + nums2[0].
- Heap: [(1+2, 0,0), (7+2, 1,0), (11+2, 2,0)]
- Pop (3, 0,0). Result [[1,2]]. Push (1+4, 0,1).
- Pop (5, 0,1). Result [[1,2], [1,4]]. Push (1+6, 0,2).
- Pop (7, 0,2). Result [[1,2], [1,4], [1,6]]. Done.
Sort Characters By Frequency
Given a string s, sort it in decreasing order based on the frequency of the characters. The frequency of a character is the number of times it appears in the string.
Return the sorted string. If there are multiple answers, return any of them.
Visual Representation
s = "tree"
Frequency Count:
'e': 2
't': 1
'r': 1
Sorted by frequency: 'e' -> 't' -> 'r'
Result: "eetr" or "eert"Examples
Level I: Max-Heap (Standard)
Intuition
Count the frequency of each character. Then, construct a Max-Heap where the key is the frequency. Repeatedly pop the character with the highest frequency and append it to our result string the required number of times.
Thought Process
- Use a hash map to count character frequencies.
- Add all
(frequency, character)pairs to a Max-Heap. - Poll characters from the heap and append each to a
StringBuilderfrequencytimes. - Return the constructed string.
Detailed Dry Run
s = "tree"
- Map: {'t':1, 'r':1, 'e':2}
- Max-Heap: [('e',2), ('t',1), ('r',1)]
- Pop ('e',2). Appended 2 times -> "ee"
- Pop ('t',1). Appended 1 time -> "eet"
- Pop ('r',1). Appended 1 time -> "eetr" Result: "eetr"
Level II: Sorting Map Entries
Intuition
Collect character frequencies in a map, convert the map entries to a list, and sort the list by frequency in descending order. Then build the string based on the sorted list.
Detailed Dry Run
s = "tree"
- Map: {'t':1, 'r':1, 'e':2}
- List of entries: [('t',1), ('r',1), ('e',2)]
- Sorted List: [('e',2), ('t',1), ('r',1)]
- Result: "eetr"
Level III: Bucket Sort (Optimal)
Intuition
Since the maximum frequency a character can have is the length of the string N, we can use an array of lists as buckets. bucket[i] stores characters that appear exactly i times.
Detailed Dry Run
s = "tree", N=4
- Map: {'t':1, 'r':1, 'e':2}
- Buckets array of size 5: [0:[], 1:['t','r'], 2:['e'], 3:[], 4:[]]
- Iterate from 4 down to 1.
- i=2: bucket contains 'e'. Append 'e' 2 times -> "ee"
- i=1: bucket contains 't','r'. Append 't' 1 time, 'r' 1 time -> "eetr"
Minimum Cost to Connect Sticks
You have some number of sticks with positive integer lengths. These lengths are given as an array sticks, where sticks[i] is the length of the ith stick.
You can connect any two sticks of lengths x and y into one stick by paying a cost of x + y. You must connect until there is only one stick remaining.
Return the minimum cost of connecting all the given sticks into one stick in this way.
Visual Representation
sticks = [2,4,3]
Options:
1. Connect 2 and 3: cost = 5, sticks = [4, 5]
Connect 4 and 5: cost = 9, sticks = [9]
Total cost: 5 + 9 = 14 (Optimal)
2. Connect 4 and 3: cost = 7, sticks = [2, 7]
Connect 2 and 7: cost = 9, sticks = [9]
Total cost: 7 + 9 = 16 (Not Optimal)Examples
Level I: Sorting Repeatedly (Simulation)
Intuition
To minimize the total cost, we intuitively always want to connect the two currently smallest sticks. We can simulate this by sorting the array, removing the two smallest, adding their sum to the cost, putting the new stick back, and re-sorting.
Thought Process
- If
sticks.length <= 1, cost is 0. - Loop while we have more than 1 stick.
- Sort the array/list.
- Take the first two elements and add them to get
currentCost. - Add
currentCosttototalCost. - Remove the first two elements and insert
currentCost. - Repeat until 1 stick remains.
Detailed Dry Run
sticks = [1,8,3,5]
- Sort: [1, 3, 5, 8]
- Take 1, 3. Sum = 4. Cost = 4. Array [4, 5, 8]
- Sort: [4, 5, 8]
- Take 4, 5. Sum = 9. Cost = 4 + 9 = 13. Array [9, 8]
- Sort: [8, 9]
- Take 8, 9. Sum = 17. Cost = 13 + 17 = 30. Total = 30.
Level II: Binary Search Insertion (Optimized Simulation)
Intuition
Maintain the list in sorted order by using binary search to find the insertion point for each new combined stick.
Detailed Dry Run
sticks = [1,8,3,5]
- Sort: [1, 3, 5, 8]
- 1+3=4. Binary search 4 in [5, 8] -> index 0. List: [4, 5, 8].
- 4+5=9. Binary search 9 in [8] -> index 1. List: [8, 9].
- 8+9=17. Total: 4+9+17=30.
Level III: Min-Heap (Optimal)
Intuition
To minimize the cost, we should always combine the two smallest sticks available. A Min-Heap is the perfect data structure for this, as it allows us to efficiently extract the two smallest elements and insert their sum.
Thought Process
- Push all stick lengths into a Min-Heap.
- While the heap contains more than one stick:
- Extract the two smallest sticks (
s1,s2). - Calculate their sum (
cost = s1 + s2). - Add
costto thetotalCost. - Push the new combined stick (
cost) back into the heap.
- Extract the two smallest sticks (
- Once only one stick remains in the heap, return
totalCost.
Detailed Dry Run
sticks = [1,8,3,5]
- Heap: [1, 3, 5, 8], Cost: 0
- Pop 1, 3. Sum = 4. Cost = 4. Push 4. Heap: [4, 5, 8]
- Pop 4, 5. Sum = 9. Cost = 4 + 9 = 13. Push 9. Heap: [8, 9]
- Pop 8, 9. Sum = 17. Cost = 13 + 17 = 30. Push 17. Heap: [17]
- One element left. Return 30.
Find Median from Data Stream
The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.
Implement the MedianFinder class:
MedianFinder()initializes theMedianFinderobject.void addNum(int num)adds the integernumfrom the data stream to the data structure.double findMedian()returns the median of all elements so far. Answers within10^-5of the actual answer will be accepted.
Visual Representation
Stream: [2, 3, 4]
addNum(2) -> [2] (Median = 2.0)
addNum(3) -> [2, 3] (Median = (2+3)/2 = 2.5)
addNum(4) -> [2, 3, 4] (Median = 3.0)
Two Heaps State:
Lower (Max-Heap) | Upper (Min-Heap)
[2] | []
[2] | [3]
[2, 3](pop->[2]) | [3, 4](push 3)
[2, 3] | [4]Examples
Level I: Array and Insertion Sort
Intuition
Maintain a dynamically growing array. Every time a new number is added, keep the array sorted by placing the new number in its correct position. When calculating the median, simply access the middle element(s).
Thought Process
- Store numbers in a standard list or array.
- On
addNum(n):- Find the correct position to insert
nso the collection remains sorted. - Insert
n.
- Find the correct position to insert
- On
findMedian():- If the size is odd, return
list[size / 2]. - If the size is even, return
(list[size / 2 - 1] + list[size / 2]) / 2.0.
- If the size is odd, return
Detailed Dry Run
Calls: add(1), add(2), find(), add(3), find()
- add(1): Array = [1]
- add(2): Insert 2. Array = [1, 2]
- find(): Even size 2. Return (1+2)/2.0 = 1.5
- add(3): Insert 3. Array = [1, 2, 3]
- find(): Odd size 3. Middle is index 1. Return 2.0
Level II: TreeMap of Counts
Intuition
Use a sorted map (TreeMap in Java, SortedDict in Python) to store the frequency of each number. To find the median, we iterate through the map to find the middle element(s) based on the total count.
Detailed Dry Run
Calls: add(1), add(2), find(), add(3), find()
- add(1): map={1:1}, total=1
- add(2): map={1:1, 2:1}, total=2
- find(): total=2. Median is at pos 1, 2. Summing counts: at 1, count=1(pos 0-0), at 2, count=1(pos 1-1). Avg of 1 and 2 is 1.5.
- add(3): map={1:1, 2:1, 3:1}, total=3
- find(): total=3. Median is at pos 1. Summing: pos 1 is val 2. Return 2.0
Level III: Two Heaps (Optimal)
Intuition
To find the median instantly, we only need access to the middle one or two values. We can split the stream into a lower half (stored in a Max-Heap) and an upper half (stored in a Min-Heap). The Max-Heap holds the maximum of the lower values, while the Min-Heap holds the minimum of the upper values.
Thought Process
low: A Max-Heap to store the smaller half of the numbers.high: A Min-Heap to store the larger half of the numbers.- When adding
num:- Always push to
low, then pop the largest fromlowand push it tohigh(to ensure every element inlowis smaller than elements inhigh). - Balance the heaps: If
highhas more elements thanlow, pophighand push tolow.
- Always push to
- When finding median:
- If sizes are unequal, the extra element is in
low, so return the top oflow. - If equal, return
(low.top() + high.top()) / 2.0.
- If sizes are unequal, the extra element is in
Detailed Dry Run
add(1): low pulls 1, pushes to high; high size > low, wait, step-by-step:
- Add 1: Push to low -> low=[1]. Pop low (1) -> Push to high. high=[1]. high size(1) > low size(0) -> Pop high(1) -> push to low. low=[1], high=[].
- Add 2: Push to low -> low=[2,1]. Pop low(2) -> push to high. high=[2], low=[1]. Sizes equal.
- Median: (1 + 2)/2.0 = 1.5
- Add 3: Push to low -> low=[3,1]. Pop low(3) -> push to high. high=[2,3], low=[1]. high size(2) > low size(1) -> Pop high(2) -> push to low. low=[2,1], high=[3].
- Median: low has extra. Return 2.0.
Sliding Window Maximum
You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the max sliding window.
Visual Representation
nums = [1,3,-1,-3,5,3,6,7], k = 3
Window position Max
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7Examples
Level I: Max-Heap (Lazy Removal)
Intuition
A Max-Heap easily gives us the maximum element. However, when the window slides, removing the element that left the window from the middle of the heap is O(N). Instead, we can use lazy removal: we store (value, index) in the heap. We only remove elements from the top of the heap if their index is outside the current window's bounds.
Thought Process
- Use a Max-Heap storing pairs of
(nums[i], i). - Initialize the heap with the first
kelements. - The max of the first window is the top of the heap.
- Slide the window from
i = ktoN - 1:- Push
(nums[i], i)into the heap. - Check the top of the heap. If its index is
<= i - k, it means it's outside the window. Pop it. - Keep popping until the top is inside the window.
- Record the top's value as the max for the current window.
- Push
Detailed Dry Run
nums = [1,3,-1,-3,5,3], k = 3
- Init k=3: Heap = [(3,1), (1,0), (-1,2)]. Max = 3.
- i=3 (val=-3): Push (-3,3). Heap = [(3,1), (1,0), (-1,2), (-3,3)]. Top idx=1 is inside window (3-3=0, 1>0). Max = 3.
- i=4 (val=5): Push (5,4). Heap = [(5,4), (3,1),...]. Top idx=4 is inside. Max = 5.
- i=5 (val=3): Push (3,5). Heap = [(5,4), ...]. Top idx=4 is inside. Max = 5.
Level II: Balanced BST / Sorted List
Intuition
Maintain a sorted collection of the elements currently in the sliding window. As the window slides, remove the element that is falling out and insert the new element. The maximum is always the last element in the sorted collection.
Detailed Dry Run
nums = [1,3,-1], k = 3
- Initial window [1, 3, -1]. Sorted: [-1, 1, 3]. Max = 3.
- Slide to [3, -1, -3]: Remove 1, Insert -3. Sorted: [-3, -1, 3]. Max = 3.
- Slide to [-1, -3, 5]: Remove 3, Insert 5. Sorted: [-3, -1, 5]. Max = 5.
Level III: Monotonic Deque (Optimal)
Intuition
If we have a descending sequence in the window, say [5, 4, 2], the maximum is 5. If we add a new number 6, then 5, 4, 2 are completely useless because they are smaller than 6 and will leave the window before 6 does! Thus, a monotonically decreasing deque can perfectly track the useful candidates for being the window's max.
Thought Process
- Maintain a deque (double-ended queue) of
indices. - The elements represented by indices in the deque are kept in strictly descending order.
- Loop over
nums[i]from0toN-1:- Maintain Window Bound: If the index at the
frontof the deque is out of the current window (< i - k + 1), remove it from the front. - Maintain Descending Order: While the deque is not empty and the element at the
backis< nums[i], remove it from the back. (They are useless now). - Add current index
ito the back. - Once
i >= k - 1, the window is fully formed. The maximum is always at thefrontof the deque, so appendnums[deque.front()]to the result.
- Maintain Window Bound: If the index at the
Detailed Dry Run
nums = [1,3,-1,-3,5,3], k = 3
- i=0, v=1: dq=[0 (val:1)]
- i=1, v=3: 1 < 3, pop 0. dq=[1 (val:3)]
- i=2, v=-1: -1 < 3, keep. dq=[1, 2]. Window full, max=nums[1]=3
- i=3, v=-3: -3 < -1, keep. dq=[1, 2, 3]. max=nums[1]=3
- i=4, v=5: front 1 out of bounds (1 <= 4-3). pop 1. val 5 > -1, -3. pop 2, 3. push 4. dq=[4 (val:5)]. max=5
Trapping Rain Water II
Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the volume of water it can trap after raining.
Visual Representation
heightMap =
[1, 4, 3, 1, 3, 2]
[3, 2, 1, 3, 2, 4]
[2, 3, 3, 2, 3, 1]
Water trapped at (1,1) (height 2): bounded by 3, 4, 3, 3. Can hold 1 unit.
Water trapped at (1,2) (height 1): bounded by 3, 2, 3, 3. Can hold 2 units.
Total trapped = 14 units.
Min-Heap expanding from boundaries:
Push all boundary cells to Min-Heap.
Always pop the lowest boundary (weakest link) to see if water can flow into its neighbors.Examples
Level I: Iterative Water Level Updates
Intuition
The water level at any cell (r, c) is determined by the maximum of its own height and the minimum water level of its neighbors. We can start by assuming every inner cell can hold infinite water, and boundary cells hold exactly their height. Then, we iteratively relax (update) the inner cells until the water levels stabilize (no more changes occur), similar to the Bellman-Ford algorithm.
Detailed Dry Run
Grid 3x3 (Heights): 3 3 3 3 1 3 3 3 3
Init Water: 3 3 3 3 INF 3 3 3 3
Iter 1: Water[1,1] = max(H[1,1]=1, min(W[0,1]=3, W[2,1]=3, W[1,0]=3, W[1,2]=3)) = max(1, min(3,3,3,3)) = 3 Changes = True. Next loop Changes = False. Trapped at (1,1) = Water[1,1] - H[1,1] = 3 - 1 = 2.
Level II: 4-Way Boundary Scanning (Upper Bound Heuristic)
Intuition
In the 1D version, the water level at any index is min(maxLeft, maxRight). We can extend this logic to 2D by calculating the maximum height seen from all four cardinal directions (Left, Right, Top, Bottom) for each cell. The water level at (r, c) can be at most the minimum of these four peaks. While this ignores diagonal leaks, it provides an heuristic that is faster than iterative relaxation.
Detailed Dry Run
Grid 3x3: 3 3 3 3 1 3 3 3 3 For cell (1,1):
- MaxLeft = 3, MaxRight = 3, MaxUp = 3, MaxDown = 3
- Level = min(3,3,3,3) = 3
- Trapped = 3 - 1 = 2 (Correct in this case).
Level III: Min-Heap + BFS (Optimal)
Intuition
Water flows from high places to low places, but its maximum volume is gated by the weakest link (lowest boundary) surrounding it. If we start from the outer boundaries and always process the lowest boundary segment inwards (using a Min-Heap), we simulate how water would spill over. If the neighbor is lower than our current boundary, it fills up with water to meet our boundary height.
Detailed Dry Run
Grid: 3 3 3 3 1 3 3 3 3
- Push all boundaries to Min-Heap: (3,0,0),(3,0,1)... Visited boundary.
- Pop (3,0,1). Check neighbor (1,1). It's unvisited. Height is 1.
- Inner height 1 < Boundary height 3. Trapped = 3 - 1 = 2.
- Push neighbor (1,1) into Heap with updated height
max(1, 3) = 3, mark visited. - Continue popping. Other neighbors of (1,1) are boundaries and visited. Return total trapped = 2.
Minimum Cost to Hire K Workers
There are n workers. You are given two integer arrays quality and wage where quality[i] is the quality of the ith worker and wage[i] is the minimum wage expectation for the ith worker.
We want to hire exactly k workers to form a paid group. To hire a group of k workers, we must pay them according to the following rules:
- Every worker in the paid group should be paid in the ratio of their quality compared to other workers in the paid group.
- Every worker in the paid group must be paid at least their minimum wage expectation.
Return the least amount of money needed to form a paid group satisfying the above conditions.
Visual Representation
quality = [10, 20, 5], wage = [70, 50, 30], k = 2
Ratios (wage/quality):
Worker 0: 70/10 = 7.0
Worker 1: 50/20 = 2.5
Worker 2: 30/5 = 6.0
Sorted by ratio:
[W1(2.5, q:20), W2(6.0, q:5), W0(7.0, q:10)]
To hire W2 and W1, base ratio is 6.0.
Cost = 6.0 * (20 + 5) = 150.0
To hire W0 and W2, base ratio is 7.0.
Cost = 7.0 * (10 + 5) = 105.0
Min cost = 105.0Examples
Level I: Sort by Ratio and Brute Force K-Smallest
Intuition
The total pay for K workers is determined by the highest wage / quality ratio among the chosen K workers. If we sort workers by this ratio, when we consider the i-th worker in the sorted list as having the maximum ratio, any K-1 workers before i can be hired at this ratio. To minimize cost, we should simply pick the K-1 workers with the smallest qualities from 0 to i-1.
Thought Process
- Pair each worker's quality with their
wage/qualityratio. - Sort all workers by their ratio in ascending order.
- Iterate
ifromk - 1ton - 1(a valid window to pickkworkers). - For each
i, the ratio isworker[i].ratio. - We need to pick
kqualities from index0toi(includingworker[i].quality) that are the smallest. Sinceworker[i]must be included, we pickworker[i].quality+k-1smallest qualities from0toi-1. - For a brute force approach, extract all qualities from
0toi, sort them, sum the firstk, and multiply by the ratio. - Keep track of the minimum cost.
Detailed Dry Run
quality = [10,20,5], wage = [70,50,30], k = 2 Ratios sorted: [(2.5,20), (6.0,5), (7.0,10)]
- i=1 (ratio=6.0, q=5). We need 2 qualities from idx 0 to 1. Qualities: [20, 5]. Sorted: [5, 20]. Sum = 25. Cost = 6.0 * 25 = 150.0. Min = 150.0
- i=2 (ratio=7.0, q=10). We need 2 qualities from idx 0 to 2. Qualities: [20, 5, 10]. Sorted: [5, 10, 20]. Sum = 5 + 10 = 15. Cost = 7.0 * 15 = 105.0. Min = 105.0
Level II: Sort by Ratio + BST/TreeMap for Qualities
Intuition
As we iterate through workers sorted by ratio, we just need the smallest qualities we've seen so far. Instead of sorting all qualities at every step, we can maintain them in a sorted data structure (TreeMap in Java, multiset in C++, SortedList in Python). This reduces the cost of finding the sum of the smallest qualities to or better per step.
Detailed Dry Run
quality = [10,20,5], wage = [70,50,30], k = 2 Ratios sorted: [(2.5,20), (6.0,5), (7.0,10)]
- i=1 (ratio=6.0, q=5): BST = {5, 20}. Sum of 2 smallest = 25. Cost = 6.0 * 25 = 150.0.
- i=2 (ratio=7.0, q=10): BST = {5, 10, 20}. Sum of 2 smallest = 15. Cost = 7.0 * 15 = 105.0.
Level III: Max-Heap (Optimal)
Intuition
Why sort qualities again and again? We just need the K smallest qualities seen so far. As we iterate through the workers sorted by ratio, we can maintain a Max-Heap of size K. If we add a new quality and the heap size exceeds K, we pop the maximum quality out. This seamlessly keeps track of the sum of the K smallest qualities in O(log K) time per worker.
Detailed Dry Run
quality = [10,20,5], wage = [70,50,30], k = 2
Workers sorted: [(2.5, q:20), (6.0, q:5), (7.0, q:10)]
Wait, k=2. Max-Heap pq, quality_sum = 0
- i=0, (2.5, 20): push 20. pq=[20], sum=20. size 1 < 2. Cost: ignore.
- i=1, (6.0, 5): push 5. pq=[20,5], sum=25. size 2 == 2. Cost = 25 * 6.0 = 150.0. Min = 150.0
- i=2, (7.0, 10): push 10. sum=35. pq=[20,10,5]. size 3 > 2 -> pop max (20). sum = 35 - 20 = 15. Size is now 2. Cost = 15 * 7.0 = 105.0. Min = min(150, 105) = 105.0. Return 105.0
IPO
Suppose LeetCode will start its IPO soon. In order to sell a good price of its shares to Venture Capital, LeetCode would like to work on some projects to increase its capital before the IPO. Since it has limited resources, it can only finish at most k distinct projects before the IPO. Help LeetCode design the best way to maximize its total capital after finishing at most k distinct projects.
You are given n projects where the ith project has a pure profit profits[i] and a minimum capital of capital[i] is needed to start it.
Initially, you have w capital. When you finish a project, you will obtain its pure profit and the profit will be added to your total capital. Return the maximized final capital.
Visual Representation
k = 2, w = 0, profits = [1, 2, 3], capital = [0, 1, 1]
Initial Capital w = 0
Available Projects (capital <= 0): Project 0 (profit 1)
Do Project 0 -> w = 0 + 1 = 1
Projects left: 1 (cap:1, prof:2), 2 (cap:1, prof:3)
Available Projects (capital <= 1): Project 1, Project 2
Pick max profit: Project 2 (profit 3)
Do Project 2 -> w = 1 + 3 = 4
Max Capital = 4Examples
Level I: Brute Force Greedy Selection
Intuition
To maximize our final capital, we should always greedily choose the project that we can afford (capital <= current w) and that yields the maximum profit. Once we finish it, our w increases, potentially unlocking new jobs. We can simply scan the list of projects up to k times, picking the best affordable project each time.
Detailed Dry Run
k=2, w=0, prof=[1,2,3], cap=[0,1,1]
- k=1: Scan all. P0(0<=0, prof 1), P1(1>0), P2(1>0). Best is P0. w=0+1=1. Mark P0 used.
- k=2: Scan all. P0(used), P1(1<=1, prof 2), P2(1<=1, prof 3). Best is P2. w=1+3=4. Mark P2 used. Return w=4.
Level II: Sort Projects by Profit and Scan
Intuition
To improve our search for the maximum profit, we can sort all projects by their profit in descending order. Then, for each selection, we scan this sorted list from the beginning and pick the first project that we can afford (capital <= w). Once picked, we mark it used and repeat.
Detailed Dry Run
k=2, w=0, prof=[1,2,3], cap=[0,1,1] Sorted by Profit (desc): [(3,1), (2,1), (1,0)]
- k=1: Scan. (3, cap:1 > 0) skip. (2, cap:1 > 0) skip. (1, cap:0 <= 0) PICK. w = 0 + 1 = 1.
- k=2: Scan. (3, cap:1 <= 1) PICK. w = 1 + 3 = 4. Return w = 4.
Level III: Two Heaps (Optimal)
Intuition
Instead of scanning the whole array every time, we can maintain two collections: one for projects we CAN'T afford yet (sorted by capital required), and one for projects we CAN afford (sorted by profit). As our capital w grows, we transfer projects from the "can't afford" group to the "can afford" group. This perfectly maps to using a Min-Heap for capital and a Max-Heap for profits.
Detailed Dry Run
k=2, w=0, prof=[1,2,3], cap=[0,1,1]
- Pair & Sort by Capital: [(0,1), (1,2), (1,3)] -> Min-Heap equivalent.
- w=0. Move affordable to Max-Heap (profits). Max-Heap gets (profit=1) from (0,1). Max-Heap = [1].
- Can't move (1,2) or (1,3) since 1 > 0.
- Do job 1 (k=1): pop Max-Heap (1). w = 0 + 1 = 1.
- New w=1. Check Min-Heap. Both (1,2) and (1,3) are now affordable. Push their profits (2, 3) to Max-Heap. Max-Heap = [3, 2].
- Do job 2 (k=2): pop Max-Heap (3). w = 1 + 3 = 4.
- k=2 reached. Return w=4.
Kth Smallest Element in Sorted Matrix
Given an n x n matrix where each of the rows and columns is sorted in ascending order, return the kth smallest element in the matrix.
Note that it is the kth smallest element in the sorted order, not the kth distinct element.
Visual Representation
matrix =
[ 1, 5, 9 ]
[ 10, 11, 13 ]
[ 12, 13, 15 ]
k = 8
All elements sorted: [1, 5, 9, 10, 11, 12, 13, 13, 15]
The 8th smallest is 13.
Min-Heap approach (similar to Merge K sorted lists):
Initial heap: [(1,0,0)] <- (val, row, col)
Expand smallest, add right neighbor and maybe downExamples
Level I: Flatten and Sort
Intuition
The simplest approach is to collect all elements from the matrix into a flat list, sort them, and return the element at index k - 1. While not leveraging the sorted property of the matrix, this is intuitive and correct.
Detailed Dry Run
matrix = [[1,5,9],[10,11,13],[12,13,15]], k=8
- Flatten: [1, 5, 9, 10, 11, 13, 12, 13, 15]
- Sort: [1, 5, 9, 10, 11, 12, 13, 13, 15]
- Return sorted[k-1] = sorted[7] = 13
Level II: Binary Search on Value Range
Intuition
Since the matrix is sorted, any element X has a predictable number of elements smaller than or equal to it. We can binary search for the value X in the range [matrix[0][0], matrix[n-1][n-1]]. For each middle value, we count how many elements are <= mid in time using the sorted property.
Detailed Dry Run
matrix = [[1,5,9],[10,11,13],[12,13,15]], k=8 Range: [1, 15], Mid = 8 Count <= 8: (1, 5) from row 0, none from others. Count = 2. 2 < 8, search [9, 15]. Mid = 12. Count <= 12: (1,5,9), (10,11), (12). Count = 6. 6 < 8, search [13, 15]. Mid = 14. Count <= 14: All but 15. Count = 8. 8 == 8, result could be 14, search [13, 13]. Finally converge to 13.
Level III: Min-Heap (K Merged Sorted Lists)
Intuition
The matrix is essentially N sorted rows that we want to merge. We can use the same technique as "Merge K Sorted Lists": start a Min-Heap with the first element of each row. Pop the minimum, push its right neighbor. After k pops, the last popped element is the answer. Optimized: we only need to start with the first column (N elements) and expand row by row.
Detailed Dry Run
matrix = [[1,5,9],[10,11,13],[12,13,15]], k=8 Heap = [(1,r:0,c:0), (10,r:1,c:0), (12,r:2,c:0)]
- Pop (1). Push right (5,0,1). Heap=[(5,0,1),(10,1,0),(12,2,0)]. Count=1.
- Pop (5). Push (9,0,2). Count=2.
- Pop (9). No right (col 3 out of range). Count=3.
- Pop (10). Push (11,1,1). Count=4.
- Pop (11). Push (13,1,2). Count=5.
- Pop (12). Push (13,2,1). Count=6.
- Pop (13). Push (13,1,3) - out of range. Count=7.
- Pop (13). Count=8. Return 13.
Maximum Performance of a Team
You are given two integers n and k and two integer arrays speed and efficiency both of length n. There are n engineers numbered from 1 to n. speed[i] and efficiency[i] represent the speed and efficiency of the ith engineer respectively.
Choose at most k engineers out of the n engineers to form a team with the maximum performance.
The performance of a team is the sum of their speeds multiplied by the minimum efficiency among their engineers.
Return the maximum performance of this team. Since the answer can be a huge number, return it modulo 10^9 + 7.
Visual Representation
n=6, k=2, speed=[2,10,3,1,5,8], efficiency=[5,4,3,9,7,2]
Key Insight: Sort by efficiency descending.
For each engineer i as the "min efficiency" of the group,
pick the k fastest engineers from 0..i (including i).
Sorted by eff desc: (9,1),(7,5),(5,2),(4,10),(3,3),(2,8)
- Min eff = 9: Team = [(1,9)]. Perf = 1*9 = 9
- Min eff = 7: Team = [(1,9),(5,7)]. Sum speeds=6. Perf=6*7=42
- Min eff = 5: Team = [(2,5),(5,7),(1,9)]. Speeds=[1,5,2]. Sum k=2 fastest=5+2=7. Perf=7*5=35
- Min eff = 4: Team picks (10,4) as one, need 2 fastest. Max perf = (10+5)*4 = 60Examples
Level I: Brute Force - Check All Subsets
Intuition
Try all possible subsets of at most k engineers from the n engineers. For each subset, compute performance = (sum of speeds) * (minimum efficiency). Return the maximum. This is correct but exponential in complexity.
Detailed Dry Run
n=4, k=2, speed=[2,10,3,1], efficiency=[5,4,3,9] Subsets of size 1: {0}->25=10, {1}->104=40, {2}->33=9, {3}->19=9 Subsets of size 2: {0,1}->12min(5,4)=48, {0,2}->5min(5,3)=15, {0,3}->3min(5,9)=15, {1,2}->13min(4,3)=39, {1,3}->11min(4,9)=44, {2,3}->4min(3,9)=12 Max = 48
Level II: Sort by Efficiency and Search K-Fastest
Intuition
To improve on brute force, we can sort the engineers by their efficiency in descending order. By doing so, for each engineer i, if we consider them as the minimum efficiency in the team, we only need to look at engineers from 0 to i (who all have efficiency >= eff[i]) and pick the k fastest ones.
Detailed Dry Run
n=4, k=2, speed=[2,10,3,1], efficiency=[5,4,3,9] Sorted: (9,1), (5,2), (4,10), (3,3)
- i=0, Eff=9: prefix=[1]. Sum k=2 fastest = 1. Perf = 1*9=9.
- i=1, Eff=5: prefix=[1,2]. Sum k=2 fastest = 3. Perf = 3*5=15.
- i=2, Eff=4: prefix=[1,2,10]. Sum k=2 fastest = 12. Perf = 12*4=48.
- i=3, Eff=3: prefix=[1,2,10,3]. Sum k=2 fastest = 13. Perf = 13*3=39. Max = 48.
Level III: Sort by Efficiency + Min-Heap (Optimal)
Intuition
Key Insight: When we pick any subset of engineers, the performance is bottlenecked by the minimum efficiency. If we fix the minimum efficiency to be efficiency[i], then we should include engineer i in the team AND pick the k-1 fastest engineers from all engineers who have efficiency >= efficiency[i].
If we sort engineers by efficiency in descending order, as we iterate through them, the current engineer i has the minimum efficiency seen so far. We need the maximum sum of speeds from engineer 0 to i. We maintain a Min-Heap of size k storing speeds — when the heap exceeds k, we pop the smallest speed. This ensures our heap always holds the k largest speeds.
Detailed Dry Run
n=6, k=2, sp=[2,10,3,1,5,8], eff=[5,4,3,9,7,2] Sort by eff desc: [(9,1),(7,5),(5,2),(4,10),(3,3),(2,8)] min_heap=[], speed_sum=0, best=0
- (9,sp=1): push 1. sum=1. size=1<=2. perf=1*9=9. best=9.
- (7,sp=5): push 5. sum=6. size=2<=2. perf=6*7=42. best=42.
- (5,sp=2): push 2. sum=8. size=3>2. pop min(1). sum=7. perf=7*5=35. best=42.
- (4,sp=10): push 10. sum=17. size=3>2. pop min(2). sum=15. perf=15*4=60. best=60.
- (3,sp=3): push 3. sum=18. size=3>2. pop min(3). sum=15. perf=15*3=45. best=60. Return 60 % MOD = 60
Minimum Interval to Include Each Query
You are given a 2D integer array intervals, where intervals[i] = [left_i, right_i] represents an inclusive interval [left_i, right_i]. You are also given an integer array queries.
The answer to the jth query is the size of the smallest interval i such that left_i <= queries[j] <= right_i. The size of an interval is equal to right_i - left_i + 1.
Return an array containing the answers to the queries.
Visual Representation
intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5]
Query 2: Intervals covering 2 -> [1,4](size 4), [2,4](size 3). Min = 3
Query 3: Intervals covering 3 -> [1,4](size 4), [2,4](size 3), [3,6](size 4). Min = 3
Query 4: Intervals covering 4 -> [1,4](size 4), [2,4](size 3), [3,6](size 4), [4,4](size 1). Min = 1
Query 5: Intervals covering 5 -> [3,6](size 4). Min = 4Examples
Level I: Brute Force - Check Every Interval Per Query
Intuition
For each query, scan all intervals to find which ones contain the query point. Among all covering intervals, choose the one with the minimum size. If no interval covers the query, return -1.
Detailed Dry Run
intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5] Query 2: [1,4] covers 2? Yes. Size = 4 [2,4] covers 2? Yes. Size = 3 [3,6] covers 2? No. [4,4] covers 2? No. Min = 3 Query 4: All 4 cover 4. Min = [4,4].size = 1
Level II: Sort Intervals by Size and Scan
Intuition
To optimize the search for the smallest interval, we can pre-sort all intervals by their size (right - left + 1). For each query, we then scan the sorted intervals and return the first one that contains the query point. Since the intervals are sorted by size, the first valid interval we find is guaranteed to be the smallest.
Detailed Dry Run
intervals = [[1,4],[2,4],[3,6],[4,4]], q = 2 Sizes: [1,4]->4, [2,4]->3, [3,6]->4, [4,4]->1 Sorted by Size: [[4,4], [2,4], [1,4], [3,6]] Query 2: [4,4] covers 2? No. [2,4] covers 2? Yes. STOP. ans = 3. Query 4: [4,4] covers 4? Yes. STOP. ans = 1.
Level III: Sort + Min-Heap (Sweep Line Approach)
Intuition
Sort both the intervals (by start) and the queries (by value). Use a pointer on intervals and a Min-Heap ordered by interval size.
Process each sorted query:
- Add all intervals whose start
<= current queryto the Min-Heap (they might cover this query). - Remove all intervals from the top of the heap whose end
< current query(they expired). - The top of the heap (if any) is the smallest valid interval covering the query.
We must store results keyed by the original query index to return answers in the right order.
Detailed Dry Run
iv = [[1,4],[2,4],[3,6],[4,4]], q = [2,3,4,5] Sort intervals by start: [[1,4],[2,4],[3,6],[4,4]] Sort queries with orig idx: [(2,0),(3,1),(4,2),(5,3)] MinHeap sorted by size (right-left+1) ptr=0
q=2: Add 1,4, 2,4. Heap=[(3,[2,4]),(4,[1,4])]. Top=3. ans[0]=3. q=3: Add 3,6. Heap=tops. Remove [2,4] as 4>=3. Top=1,4. ans[1]=3? Let's re-check. Actually heap=[(3,[2,4]),(4,[1,4]),(4,[3,6])]. 2,4 still covers 3. ans[1]=3. q=4: Add 4,4. Heap=[(1,[4,4]),...]. Remove expired: checks 2,4 (4>=4, valid), 1,4 (4>=4, valid), 3,6 (6>=4, valid). Top=4,4. ans[2]=1. q=5: No new. Remove [2,4](4<5 expired). Remove [1,4](4<5 expired). [3,6](6>=5 ok), [4,4](4<5 expired). Heap=[(4,[3,6])]. ans[3]=4.
Maximum Number of Events
You are given an array of events where events[i] = [startDay_i, endDay_i]. Every event i starts at startDay_i and ends at endDay_i.
You can attend an event i at any day d where startDay_i <= d <= endDay_i. You can only attend one event per day.
Return the maximum number of events you can attend.
Visual Representation
events = [[1,2],[2,3],[3,4]]
Day 1: [1,2] available. Attend it.
Day 2: [2,3] available. Attend it.
Day 3: [3,4] available. Attend it.
Total = 3
Key insight: Each day, attend the event that ends SOONEST (greedy).
This leaves later-ending events available for future days.Examples
Level I: Greedy - Sort by Start, Attend First Available
Intuition
Sort events by start day. On each day from 1 to maxDay, try to attend the first available event (one that hasn't been attended and whose start day <= current day). This is a greedy approach that ensures we don't miss starting opportunities.
Detailed Dry Run
events = [[1,2],[2,3],[3,4]] sorted by start Day 1: Available (not attended, start<=1, end>=1): [1,2]. Attend [1,2]. Count=1. Day 2: Available: [2,3]. Attend [2,3]. Count=2. Day 3: Available: [3,4]. Attend [3,4]. Count=3. Return 3.
Level II: Sort by Start + Daily Min-End Search
Intuition
To improve on the basic search, we sort events by their start day. Each day, we identify all events that have already started and haven't been attended. Among these available events, we pick the one that ends the soonest (earliest end day). This ensures we use up events that would expire early, leaving more flexibility for the future.
Detailed Dry Run
events = [[1,4],[1,1],[2,2]] sorted. Day 1: Available: [1,4], [1,1]. Ends are 4, 1. Min end is 1. Attend [1,1]. Count=1. Day 2: Available: [1,4], [2,2]. Ends are 4, 2. Min end is 2. Attend [2,2]. Count=2. Day 3: Available: [1,4]. Attend [1,4]. Count=3. Max = 3.
Level III: Sort by Start + Min-Heap of End Days (Optimal)
Intuition
Greedy insight: on each day, among all available events (those that have already started), we should attend the one that ends the soonest. If we sort events by start day and use a Min-Heap of end days, we can efficiently:
- Iterate over each day from 1 to maxDay.
- Add all events that start on this day to the Min-Heap.
- Remove all expired events (heap top end < today).
- If the heap isn't empty, attend the event ending soonest (pop from heap), increment count.
Detailed Dry Run
events = [[1,2],[2,3],[3,4]] sorted by start ptr=0
Day 1: Add events starting on 1: push end=2. Heap=[2]. Pop 2 (attend). Count=1. Day 2: Add events starting on 2: push end=3. Heap=[3]. Pop 3 (attend). Count=2. Day 3: Add events starting on 3: push end=4. Heap=[4]. Pop 4 (attend). Count=3. Return 3.
Data Stream as Disjoint Intervals
Given a data stream input of non-negative integers a1, a2, ..., an, summarize the numbers seen so far as a list of disjoint intervals.
Implement the SummaryRanges class:
SummaryRanges()Initializes the object with an empty stream.void addNum(int value)Adds the integervalueto the stream.int[][] getIntervals()Returns a summary of the integers in the stream currently as a list of disjoint intervals[start_i, end_i]. The answer should be sorted bystart_i.
Visual Representation
addNum(1): [1,1]
addNum(3): [1,1], [3,3]
addNum(7): [1,1], [3,3], [7,7]
addNum(2): [1,3], [7,7] <-- 2 merges [1,1] and [3,3]
addNum(6): [1,3], [6,7] <-- 6 merges with [7,7]
Key: When adding N, find and merge with adjacent intervals.
[L, N-1] + N + [N+1, R] => [L, R]Examples
Level I: Brute Force - Rebuild on Every getIntervals
Intuition
Simply store all seen numbers in a sorted list. On every getIntervals() call, rebuild the disjoint intervals from scratch by scanning through the sorted list and merging consecutive numbers.
Detailed Dry Run
addNum(1), addNum(3), addNum(7), addNum(2) nums = {1, 2, 3, 7} getIntervals(): sorted = [1, 2, 3, 7] Scan: 1->next is 2 (consecutive), 2->next is 3 (consecutive), 3->next is 7 (gap) Intervals: [1,3], [7,7]
Level II: Maintenance of Sorted Intervals
Intuition
Instead of rebuilding the intervals every time, we can maintain a sorted list of disjoint intervals. When adding a new number, we use binary search to find where it would fit among existing intervals, then check if it can be merged with its left or right neighbors. This significantly speeds up getIntervals at the cost of for addNum due to array shifts.
Detailed Dry Run
addNum(1): [[1,1]] addNum(3): [[1,1], [3,3]] addNum(2): 2 is between [1,1] and [3,3]. Merges both -> [[1,3]] addNum(7): [[1,3], [7,7]]
Level III: TreeMap / Sorted Map for O(log N) addNum
Intuition
Instead of re-sorting each time, maintain intervals in a sorted map (TreeMap in Java, SortedDict in Python, etc.) keyed by start. When adding a new number val:
- Find the interval that might start right after
val+1(right neighbor). - Find the interval whose start <=
val(left neighbor), and check if it ends atval-1. - Based on overlapping/adjacency with left and right neighbors, merge intervals accordingly.
This gives O(log N) per addNum and O(N) per getIntervals.
Detailed Dry Run
addNum(1): map = {1:[1,1]} addNum(3): map = {1:[1,1], 3:[3,3]} addNum(7): map = {1:[1,1], 3:[3,3], 7:[7,7]} addNum(2): val=2. Right neighbor start=3 (3 == 2+1). Left neighbor: key=1, [1,1]. end=1 == 2-1. Merge both: [min(1,2)=1, max(1,3)=3] = [1,3]. Remove key 3. map = {1:[1,3], 7:[7,7]} addNum(6): val=6. Right neighbor start=7 (7 == 6+1). Check left: key=1, [1,3]. end=3 != 5. Only merge with right: [6, 7]. Remove key 7. map = {1:[1,3], 6:[6,7]} getIntervals: [[1,3],[6,7]]
Smallest Range Covering Elements from K Lists
You have k lists of sorted integers in non-decreasing order. Find the smallest range that includes at least one number from each of the k lists.
We define the range [a, b] is smaller than range [c, d] if b - a < d - c, or a < c if b - a == d - c.
Visual Representation
nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
Initial elements from each list: {4, 0, 5}
Range: [0, 5], Size: 5
To find a smaller range, we must increase the minimum (0).
Next element from List 1 (was 0): 9
Set: {4, 9, 5}
Range: [4, 9], Size: 5 (same size, but 4 > 0, so not 'smaller' by definition)
Next element from List 0 (was 4): 10
Set: {10, 9, 5}
Range: [5, 10], Size: 5
...
Eventually we find [20, 24], Size: 4.Examples
Level I: Brute Force - Check All Pairs
Intuition
Pick every possible number from the lists as a potential 'start' and every other number as a potential 'end'. For each range [start, end], check if it contains at least one number from each of the k lists. Keep track of the smallest such range.
Detailed Dry Run
nums = [[4,10],[0,9],[5,18]] Pairs: [0,4], [0,5], [0,9], [0,10], [4,5], [4,9]... Range [0,5]: Contains 4(L0), 0(L1), 5(L2). Valid. Size 5. Range [4,9]: Contains 4(L0), 9(L1), 5(L2). Valid. Size 5. ...
Level II: Sliding Window on All Elements
Intuition
Merge all numbers into a single sorted list, keeping track of which list each number came from. Now the problem becomes: find the shortest subarray in this merged list that contains at least one number from every original list. This is a classic sliding window problem (similar to 'Smallest Window Containing All Characters').
Detailed Dry Run
nums = [[4,10],[0,9],[5,18]] Merged: [(0,L1), (4,L0), (5,L2), (9,L1), (10,L0), (18,L2)] Window [0, 5]: L1, L0, L2 present. Size 5-0=5. Window [4, 9]: L0, L2, L1 present. Size 9-4=5. Window [5, 10]: L2, L1, L0 present. Size 10-5=5. Window [9, 18]: L1, L0, L2 present. Size 18-9=9.
Level III: K-Way Min-Heap Search (Optimal)
Intuition
To find the smallest range, we need to track one element from each list. We maintain these k elements in a Min-Heap. The current range is defined by [min_in_heap, max_in_heap]. To try and find a smaller range, we must increase the minimum element by popping it and replacing it with the next element from that same list. As we go, we keep track of the maximum value currently in our set to update the range.
Detailed Dry Run
nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]] Heap = [(0,L1,idx0), (4,L0,idx0), (5,L2,idx0)]. MaxVal = 5. Range = [0, 5], Size 5.
- Pop 0. Next L1 is 9. Heap=[(4,0,0), (5,2,0), (9,1,1)]. MaxVal = 9. Range=[4,9], Size 5.
- Pop 4. Next L0 is 10. Heap=[(5,2,0), (9,1,1), (10,0,1)]. MaxVal = 10. Range=[5,10], Size 5.
- Pop 5. Next L2 is 18. Heap=[(9,1,1), (10,0,1), (18,2,1)]. MaxVal = 18. Range=[9,18], Size 9. ... Eventually we hit [20, 24].
Kth Smallest Prime Fraction
You are given a sorted integer array arr containing 1 and prime numbers, where all the integers of arr are unique. You are also given an integer k.
For every i and j where 0 <= i < j < arr.length, we consider the fraction arr[i] / arr[j].
Return the kth smallest fraction considered. Return your answer as an array of integers of size 2, where answer[0] = arr[i] and answer[1] = arr[j].
Visual Representation
arr = [1, 2, 3, 5], k = 3
All fractions:
1/5, 1/3, 2/5, 1/2, 3/5, 2/3
Sorted: 1/5, 1/3, 2/5, 1/2, 3/5, 2/3
3rd smallest: 2/5
Heap strategy: Start with 1/5, 1/3, 1/2.
Pop smallest (1/5), push next numerator: 2/5.
Pop smallest (1/3), push next: 2/3.
Pop smallest (2/5). This is the 3rd pop -> Result [2, 5].Examples
Level I: Brute Force - Generate and Sort
Intuition
Generate all possible fractions arr[i] / arr[j] where i < j. Store them in a list, sort the list by fraction value, and return the kth element. Simple but slow for large arrays.
Detailed Dry Run
arr = [1, 2, 3, 5], k = 3 Fractions: 1/2, 1/3, 1/5, 2/3, 2/5, 3/5 Values: 0.5, 0.33, 0.2, 0.66, 0.4, 0.6 Sorted values: 0.2(1/5), 0.33(1/3), 0.4(2/5), 0.5(1/2), 0.6(3/5), 0.66(2/3) 3rd smallest: [2, 5].
Level II: Binary Search on Value Range
Intuition
The value of any fraction is between 0 and 1. We can binary search for a value X such that there are exactly k fractions smaller than or equal to X. For a given X, we can count how many fractions satisfy arr[i] / arr[j] <= X (or arr[i] <= X * arr[j]) in time using two pointers.
Detailed Dry Run
arr = [1, 2, 3, 5], k = 3 Low=0, High=1, Mid=0.5. Count <= 0.5: 1/2, 1/3, 1/5, 2/5 (4 fractions). Count=4 > 3, High=0.5. Mid=0.25. Count <= 0.25: 1/5 (1 fraction). Count=1 < 3, Low=0.25. Converges to a value where count=3 and max fraction seen is 2/5.
Level III: Min-Heap (Sorted Pointers)
Intuition
This problem is similar to 'Merging K Sorted Lists'. For each denominator arr[j], the possible numerators arr[0], arr[1], ..., arr[j-1] create a sorted sequence of fractions. We can use a Min-Heap to store the smallest fraction for each possible denominator arr[j]. We pop the overall smallest and replace it with the next numerator from that same denominator's list.
Detailed Dry Run
arr = [1, 2, 3, 5], k = 3 Initial Heap: {1/5, 1/3, 1/2}
- Pop 1/5. Next num for denom 5 is 2. Heap: {1/3, 1/2, 2/5}. Pop count = 1.
- Pop 1/3. Next num for denom 3 is 2. Heap: {1/2, 2/5, 2/3}. Pop count = 2.
- Pop 2/5. Pop count = 3. Done. Result [2, 5].
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.