Hello! Welcome to your next lesson.
In our previous session, you learned how to implement a priority queue by leveraging the power of a binary heap. You saw that by composing a heap within a PriorityQueue class, we get a clean, abstract interface with efficient O(log n) performance for both enqueuing and dequeuing elements.
Today, we will apply this powerful abstract data type to solve concrete problems. This lesson directly addresses your goal of building an "architectural mindset" by showing you how to recognize problem patterns and map them to the appropriate data structure. Your learning outcome is to apply a priority queue to solve a selection or scheduling problem. We will tackle two classic examples: finding the k-th largest element in a collection and scheduling tasks with cooldowns.
1. The Selection Problem: Finding the k-th Largest Element
A common requirement in data analysis and monitoring is to find the "Top-K" items in a large dataset—for instance, the top 10 most visited pages on a website or the 5 most expensive transactions in a day. The core of this problem is finding the k-th largest element.
Let's formally define the problem: Given an unsorted array of numbers nums and an integer k, find the k-th largest element in the sorted order.
For example, if nums = [3, 2, 1, 5, 6, 4] and k = 2, the sorted array is [1, 2, 3, 4, 5, 6]. The largest element is 6, and the second-largest is 5. So the answer is 5.
A simple brute-force solution would be to sort the array in O(n log n) time and pick the element at index n-k. But can we do better? Since we only need one specific element, sorting the entire array seems wasteful. This is where a priority queue shines.
The Min-Heap Strategy
The most efficient and elegant solution uses a min-heap that is kept at a maximum size of k. This might seem counterintuitive—using a min-heap to find the largest elements—but the logic is sound.
The strategy is as follows:
- Create an empty min-heap.
- Iterate through each number in the input array.
- If the heap has fewer than
kelements, push the current number onto it. - If the heap already has
kelements, compare the current number with the smallest element in the heap (the heap's root, which ispeek()inO(1)time).- If the current number is larger than the heap's root, it means the current number belongs in our set of the top k elements seen so far. To make room, we
extract_min()from the heap andinsert()the current number. - If the current number is smaller or equal, we do nothing and move to the next number.
- If the current number is larger than the heap's root, it means the current number belongs in our set of the top k elements seen so far. To make room, we
- After iterating through all
nnumbers, the heap contains the k largest elements from the entire array. The root of the min-heap is the smallest of these, which is precisely the k-th largest element.

This video provides an excellent walkthrough of this strategy, along with the complexity analysis.
Kth Largest Element in an Array - Leetcode 215 - Heaps (Python)
The following video, 'Kth Largest Element in an Array' by Greg Hogg, explains two heap-based solutions. We will focus on the more efficient min-heap approach, which is a classic pattern for solving Top-K problems.
Watch the segment explaining the min-heap version from 06:10 to 10:20. Pay close attention to the logic of why maintaining a min-heap of size K correctly identifies the Kth largest element. Then, watch the code implementation from 10:20 to 11:35 to see how it's done in Python using the heapq library.
To solidify your understanding, the following article provides a clear textual explanation and summary of the key insights.
Leetcode 215. Kth Largest Element in an Array
This article, 'Leetcode 215. Kth Largest Element in an Array,' provides another excellent breakdown of the problem and the min-heap solution.
Read the sections 'Optimal Solution' and 'What We've Learned'. The first section provides the Python code and a clear explanation. The second section summarizes the key takeaways, which are crucial for recognizing this pattern in the future.
Complexity Analysis
- Time Complexity:
O(n log k). We iterate throughnelements. For each element, we might perform a heap operation (heappush,heapreplace). Since the heap's size is capped atk, each operation takesO(log k)time. - Space Complexity:
O(k). We need to store at mostkelements in our priority queue.
This is a significant improvement over the O(n log n) sorting solution, especially when k is much smaller than n, which is a common scenario in real-world applications (e.g., finding the top 100 out of a million).
Test your understanding!
You are tasked with finding the 3rd largest element (k=3) in the array [9, 4, 7, 1, 8, 5]. You are using the min-heap strategy. What are the contents of the heap after processing the number 8? And what is the final result?
Show answer
- Start with an empty min-heap
[]. - Process
9:[9](heap size < 3) - Process
4:[4, 9](heap size < 3) - Process
7:[4, 9, 7](heap size is now 3) - Process
1:1is not greater than the heap's root (4). Do nothing. Heap:[4, 9, 7]. - Process
8:8is greater than the heap's root (4). Pop4and push8. The heap becomes[7, 9, 8]after re-heapifying.- The contents of the heap after processing
8are[7, 9, 8].
- The contents of the heap after processing
- Process
5:5is not greater than the heap's root (7). Do nothing.
After iterating through the array, the final heap is [7, 9, 8]. The root is 7.
Final result: The 3rd largest element is 7.
2. The Scheduling Problem: The Task Scheduler
Priority queues are fundamental to scheduling algorithms. Operating systems use them to decide which process to run next, and network routers use them to prioritize certain types of data packets.

Let's explore a concrete problem: the Task Scheduler.
Imagine you have a CPU that needs to process a list of tasks, e.g., ['A', 'A', 'A', 'B', 'B', 'C']. Between processing two identical tasks (e.g., two 'A's), there must be a "cooldown" period of n time units. In these n units, the CPU can either process a different task or remain idle. The goal is to find the minimum total time required to finish all tasks.
The Max-Heap Strategy
To minimize total time, we must minimize idle time. The best way to do this is to adopt a greedy strategy: at any given moment, process the most frequent task that is currently available (i.e., not in a cooldown period).
A max-heap is the perfect data structure to instantly get the "most frequent task."
However, we also need to manage the cooldowns. A simple queue can help us track which tasks are temporarily unavailable and when they will be ready again.
Here is the algorithm:
- Count task frequencies: Use a hash map to count the occurrences of each task.
- Build a max-heap: Push the frequencies of all tasks into a max-heap. (In Python's
heapq, which is a min-heap, you can achieve this by pushing the negative counts). - Initialize a cooldown queue: Create a queue (like Python's
deque) to hold tasks that are cooling down. It will store pairs of(remaining_count, available_time). - Simulate time: Loop one time unit at a time.
a. Increment the currenttime.
b. Check the cooldown queue: if the task at the front of the queue has itsavailable_timeequal to the currenttime, pop it and add its count back to the max-heap.
c. If the max-heap is not empty, pop the highest-frequency task. You've just "processed" it. Decrement its count.
d. If its count is still greater than 0, add it to the cooldown queue with anavailable_timeofcurrent_time + n.
e. If the max-heap was empty, the CPU was idle for this time unit. - Continue the loop until both the max-heap and the cooldown queue are empty. The final
timeis your answer.
This algorithm elegantly models the real-world problem of resource scheduling with constraints.
Task Scheduler - Leetcode 621 - Python
The video 'Task Scheduler' by NeetCode provides a fantastic, step-by-step walkthrough of this exact greedy algorithm, showing how a max-heap and a queue work together.
First, watch from 05:46 to 11:47 to understand the high-level algorithm involving the max-heap and the cooldown queue. Then, watch the code walkthrough from 11:47 to 16:34 to see how this logic is translated into a working solution. Focus on the interplay between the two data structures.
Conclusion
In this lesson, you saw how the priority queue, which you implemented in the last session, serves as a powerful tool for solving complex problems. By choosing the right data structure, you can design algorithms that are not only correct but also efficient.
Key Takeaways:
- Problem Pattern Recognition: Identifying that a problem requires repeatedly finding an element with the highest/lowest value is a strong signal to use a priority queue.
- Top-K Selection: For finding the k-th largest elements, the
O(n log k)pattern of using a min-heap of sizekis a standard and highly effective technique. - Priority Scheduling: For scheduling problems that use a greedy approach (e.g., "process the most important job next"), a max-heap is the ideal tool to manage priorities, often paired with another structure like a queue to handle constraints like cooldowns.
- Architectural Thinking: Mapping a problem's abstract requirements—like "prioritization," "selection," or "scheduling"—to concrete data structures is a cornerstone of software design and architecture.
Preview of the Next Lesson:
We have now explored a wide array of linear and tree-based data structures. Our next topic introduces one of the most versatile and powerful concepts in computer science: graphs. In the upcoming lesson, we will learn the two primary ways to represent graphs in code—adjacency matrices and adjacency lists—and analyze the critical trade-offs between them. This will be our first step into the world of graph algorithms, which are used to model everything from social networks to navigation systems.