Master this topic with zero to advance depth.
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.
A binary heap is typically represented as an array. For an index i:
(i - 1) / 22 * i + 12 * i + 2| 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 |
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.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.
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].
Intuition
Count the frequency of each element and then sort the elements based on their count in descending order. Pick the first k elements.
(element, frequency).k elements.Detailed Dry Run
nums = [1, 1, 2], k = 1
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.
buckets of size .Detailed Dry Run
nums = [1, 1, 1, 2, 2, 3], k = 2
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.
k, pop from heap.Detailed Dry Run
nums = [1, 1, 1, 2, 2, 3], k = 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.
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
Intuition
The simplest way is to sort the entire array in descending order and return the element at index k-1.
length - k (for ascending sort) or k - 1 (for descending sort).Detailed Dry Run
nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
Intuition
Based on the Partition logic of Quicksort. We only care about the partition that contains the th largest element (index ).
Detailed Dry Run
nums = [3, 2, 1, 5, 6, 4], k = 2, target = 6-2 = 4
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.
nums:
k, pop the smallest element.Detailed Dry Run
nums = [3, 2, 1, 5, 6, 4], k = 2
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.
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
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.
Detailed Dry Run
tasks = [A,A,A,B,B,B], n = 2
Intuition
Always process the task with the highest remaining frequency. We use a single loop to calculate the size of chunks of size .
Detailed Dry Run
tasks = [A, A, A, B, B, B], n = 2
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.
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
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).
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
Intuition
Calculate the squared distance for every point, then sort all points based on these distances. Return the first k points.
k.Detailed Dry Run
points = [[1,3], [-2,2]], k = 1
Intuition
Similar to Kth Largest Element, we can use the Partition logic to find the closest points in average time.
Detailed Dry Run
points = [[3,3], [5,-1], [-2,4]], k = 2 Distances: [18, 26, 20]
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.
points.k, pop the point with the largest distance (the root).k closest points.Detailed Dry Run
points = [[3,3],[5,-1],[-2,4]], k = 2
Meeting Rooms II
Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.
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
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.
intervals by start time.heap.peek() <= current.start, pop the heap (room becomes free).Detailed Dry Run
intervals = [[0,30],[5,10],[15,20]]
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.
Detailed Dry Run
intervals = [[0,30],[5,10],[15,20]]
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.
starts and ends into separate arrays.s and e.starts[s] < ends[e], increment usedRooms and s.e and s (total rooms stays same).Detailed Dry Run
starts = [0, 5, 15], ends = [10, 20, 30]
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 "".
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
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'.
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
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
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
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.
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
Intuition
Generate all possible pairs from the two arrays, calculate their sums, and sort them. Return the first k pairs.
nums1 and nums2.k elements.Detailed Dry Run
nums1 = [1,2], nums2 = [3], k = 1
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.
[u, v] pairs based on their sum.nums1 and nums2 (limit to to avoid unnecessary checks).Detailed Dry Run
nums1 = [1, 2], nums2 = [3], k = 1
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.
(nums1[i], nums2[0], 0) for all i (or just i=0 and expand both ways) into a Min-Heap.(nums1[i], nums2[j]).(nums1[i], nums2[j+1]) into the heap.k times.Detailed Dry Run
nums1 = [1, 7, 11], nums2 = [2, 4, 6], k = 3
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.
s = "tree"
Frequency Count:
'e': 2
't': 1
'r': 1
Sorted by frequency: 'e' -> 't' -> 'r'
Result: "eetr" or "eert"Examples
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.
(frequency, character) pairs to a Max-Heap.StringBuilder frequency times.Detailed Dry Run
s = "tree"
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"
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
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.
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
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.
sticks.length <= 1, cost is 0.currentCost.currentCost to totalCost.currentCost.Detailed Dry Run
sticks = [1,8,3,5]
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]
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.
s1, s2).cost = s1 + s2).cost to the totalCost.cost) back into the heap.totalCost.Detailed Dry Run
sticks = [1,8,3,5]
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 the MedianFinder object.void addNum(int num) adds the integer num from the data stream to the data structure.double findMedian() returns the median of all elements so far. Answers within 10^-5 of the actual answer will be accepted.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
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).
addNum(n):
n so the collection remains sorted.n.findMedian():
list[size / 2].(list[size / 2 - 1] + list[size / 2]) / 2.0.Detailed Dry Run
Calls: add(1), add(2), find(), add(3), find()
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()
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.
low: A Max-Heap to store the smaller half of the numbers.high: A Min-Heap to store the larger half of the numbers.num:
low, then pop the largest from low and push it to high (to ensure every element in low is smaller than elements in high).high has more elements than low, pop high and push to low.low, so return the top of low.(low.top() + high.top()) / 2.0.Detailed Dry Run
add(1): low pulls 1, pushes to high; high size > low, wait, step-by-step:
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.
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
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.
(nums[i], i).k elements.i = k to N - 1:
(nums[i], i) into the heap.<= i - k, it means it's outside the window. Pop it.Detailed Dry Run
nums = [1,3,-1,-3,5,3], k = 3
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
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.
indices.nums[i] from 0 to N-1:
front of the deque is out of the current window (< i - k + 1), remove it from the front.back is < nums[i], remove it from the back. (They are useless now).i to the back.i >= k - 1, the window is fully formed. The maximum is always at the front of the deque, so append nums[deque.front()] to the result.Detailed Dry Run
nums = [1,3,-1,-3,5,3], k = 3
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.
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
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.
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):
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
max(1, 3) = 3, mark visited.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:
Return the least amount of money needed to form a paid group satisfying the above conditions.
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
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.
wage/quality ratio.i from k - 1 to n - 1 (a valid window to pick k workers).i, the ratio is worker[i].ratio.k qualities from index 0 to i (including worker[i].quality) that are the smallest. Since worker[i] must be included, we pick worker[i].quality + k-1 smallest qualities from 0 to i-1.0 to i, sort them, sum the first k, and multiply by the ratio.Detailed Dry Run
quality = [10,20,5], wage = [70,50,30], k = 2 Ratios sorted: [(2.5,20), (6.0,5), (7.0,10)]
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)]
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
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.
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
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]
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)]
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]
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.
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
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
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.
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)]
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.
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
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
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)
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
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.
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
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
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.
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:
<= current query to the Min-Heap (they might cover this query).< current query (they expired).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.
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
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.
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.
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:
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 integer value to 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 by start_i.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
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]
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]]
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:
val+1 (right neighbor).val (left neighbor), and check if it ends at val-1.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.
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
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. ...
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.
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.
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].
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
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].
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.
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}
Help us improve! Report bugs or suggest new features on our Telegram group.