Card 03/ 08
The Top-k Template in Python, Java and TypeScript
Push each item, and when the heap holds more than k, pop its top: what stays is the k largest.
The top-k template pushes every item into a min-heap and pops the top whenever the heap holds more than k, so what stays is the k largest. It is the replace-the-top rule in a shorter form: an item smaller than the top is pushed and popped straight back out. The highlighted lines are the ones that change between problems: the size check and the order the heap uses, which decides whether the k largest or the k smallest stay. Each listing runs on 7 2 9 4 11 5 8 with k = 3.
The top-k template in three languages
import heapq
def top_k(values: list[int], k: int) -> list[int]:
heap: list[int] = []
for value in values:
heapq.heappush(heap, value)
if len(heap) > k:
heapq.heappop(heap)
return sorted(heap)
print("3 largest:", top_k([7, 2, 9, 4, 11, 5, 8], 3))
print("3 smallest:", sorted(-v for v in top_k([-v for v in [7, 2, 9, 4, 11, 5, 8]], 3)))3 largest: [8, 9, 11]
3 smallest: [2, 4, 5]import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
public class TopK {
static List<Integer> topK(int[] values, int k, Comparator<Integer> order) {
PriorityQueue<Integer> heap = new PriorityQueue<>(order);
for (int value : values) {
heap.offer(value);
if (heap.size() > k) {
heap.poll();
}
}
List<Integer> kept = new ArrayList<>();
while (!heap.isEmpty()) {
kept.add(heap.poll());
}
return kept;
}
public static void main(String[] args) {
int[] values = {7, 2, 9, 4, 11, 5, 8};
System.out.println("3 largest: " + topK(values, 3, Comparator.naturalOrder()));
System.out.println("3 smallest: " + topK(values, 3, Comparator.reverseOrder()).reversed());
}
}3 largest: [8, 9, 11]
3 smallest: [2, 4, 5]class MinHeap<T> {
private readonly items: T[] = [];
private readonly before: (a: T, b: T) => boolean;
constructor(before: (a: T, b: T) => boolean) {
this.before = before;
}
get size(): number {
return this.items.length;
}
push(value: T): void {
const items = this.items;
items.push(value);
let i = items.length - 1;
while (i > 0 && this.before(items[i], items[(i - 1) >> 1])) {
[items[i], items[(i - 1) >> 1]] = [items[(i - 1) >> 1], items[i]];
i = (i - 1) >> 1;
}
}
pop(): T | undefined {
const items = this.items;
const top = items[0];
const last = items.pop();
if (items.length > 0 && last !== undefined) {
items[0] = last;
let i = 0;
for (let child = 1; child < items.length; child = 2 * i + 1) {
if (child + 1 < items.length && this.before(items[child + 1], items[child])) child++;
if (!this.before(items[child], items[i])) break;
[items[i], items[child]] = [items[child], items[i]];
i = child;
}
}
return top;
}
}
function topK(values: number[], k: number): number[] {
const heap = new MinHeap<number>((a, b) => a < b);
for (const value of values) {
heap.push(value);
if (heap.size > k) {
heap.pop();
}
}
const kept: number[] = [];
while (heap.size > 0) {
kept.push(heap.pop() as number);
}
return kept;
}
console.log(`3 largest: [${topK([7, 2, 9, 4, 11, 5, 8], 3).join(", ")}]`);3 largest: [8, 9, 11]Python's heapq only provides a min-heap, so the k smallest are found by pushing negated values, which turns the smallest into the largest, and negating them back. Java's PriorityQueue takes a Comparator: naturalOrder() gives a min-heap and reverseOrder() a max-heap. Printing a PriorityQueue directly does not list it in order, which is why the Java version polls the heap empty first. TypeScript has no heap in its standard library, so its tab opens with a small MinHeap class, taken from the Heaps topic in Data Structures with a comparison passed in; the template is the topK function below it. Each version is O(n log k) time and O(k) space.
← → move through sections and cards