Back to Arrays
Q13Question 13 of 109

Find the Kth Largest Element Using a Min-Heap

Key Concepts
  • Maintain a min-heap of size k; the root is always the kth largest element seen so far.
  • For each element, offer it to the heap, and if the heap exceeds size k, poll the smallest out.
  • After processing all n elements, the heap's root is the kth largest overall.
  • Time is O(n log k) and space is O(k), which beats full sorting at O(n log n) when k is small.
  • The heap approach works on streams because you never need all n elements in memory.
  • Quickselect is the expected-linear in-memory alternative but has a quadratic worst case and cannot stream.

Definition

Keep the k largest in a size-k min-heap, and its root is the kth largest, in O(n log k) time.
  • Min-heap of size k – Java's PriorityQueue is a min-heap by default, with peek returning the smallest.
  • Offer and poll – for each element, offer it; if size exceeds k, poll the smallest out.
  • Root is answer – after processing all elements, peek() returns the kth largest.
  • O(n log k) time – each offer and poll on a size-k heap is O(log k), done n times.
  • O(k) space – only k elements are held at any time, enabling stream processing.

The National Bank of India wants the third-largest transaction of the day from a stream of thousands. A clerk keeps a small tray that holds only the three biggest seen so far, arranged so the smallest of those three is at the front. For each new transaction, if it is bigger than the front, she replaces the front one. At the end, the front of the tray is exactly the third-largest.

Understanding the Concepts

What is the problem?

Given an array of numbers and a value k, find the kth largest element. In a banking context, the reporting dashboard at National Bank of India needs to surface the top few largest transactions from a high-volume stream.

Why not just sort?

Sorting costs O(n log n) and requires the entire array in memory. The min-heap approach costs O(n log k), which is faster when k is much smaller than n, and it works on streams because only k elements are held at any time.

How does the min-heap approach work?

Create a PriorityQueue (min-heap). For each element in the array, offer it to the heap. If the heap grows beyond k, poll the smallest. After all elements are processed, the heap's root is the kth largest.

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.PriorityQueue;

public class TopKFinder {

    public static BigDecimal findKthLargest(BigDecimal[] amounts, int k) {
        if (amounts == null || amounts.length < k || k <= 0) {
            throw new IllegalArgumentException("Invalid input");
        }
        PriorityQueue<BigDecimal> minHeap = new PriorityQueue<>(k);
        for (BigDecimal amount : amounts) {
            minHeap.offer(amount);
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }
        return minHeap.peek();
    }

    public static void main(String[] args) {
        BigDecimal[] dailyTxns = {
            new BigDecimal("15000.00"),
            new BigDecimal("42500.75"),
            new BigDecimal("8000.00"),
            new BigDecimal("67200.50"),
            new BigDecimal("3100.25"),
            new BigDecimal("91000.00"),
            new BigDecimal("28400.00")
        };
        int k = 3;
        BigDecimal result = findKthLargest(dailyTxns, k);
        System.out.println("The " + k + "rd largest transaction: Rs." + result);
    }
}
TEXT
The 3rd largest transaction: Rs.42500.75

Why does a min-heap work and not a max-heap?

A min-heap keeps the smallest element at the root. By maintaining only k elements, the root is the smallest of the k largest, which is exactly the kth largest. A max-heap would keep the largest at the root, which is the 1st largest, and finding the kth would require popping k times, which is less efficient for streams.

What is the time and space complexity?

  • Time: O(n log k) because each offer and poll on a heap of size k is O(log k), and you do this n times.
  • Space: O(k) because only k elements are stored at any time.
  • When k is close to n, this degrades to O(n log n), matching sorting.

What is the Quickselect alternative?

Quickselect partitions the array like quicksort but recurses into only one side. It finds the kth largest in expected O(n) time, but its worst case is O(n^2). It also requires the entire array in memory and cannot stream.

| Aspect | Min-Heap | Quickselect |
|---|---|---|
| Time (average) | O(n log k) | O(n) |
| Time (worst) | O(n log k) | O(n^2) |
| Space | O(k) | O(1) in-place |
| Streaming | Yes | No |
| Deterministic | Yes | No (randomised) |

What are common mistakes?

The most common mistake is using a max-heap instead of a min-heap. Another is forgetting to check that k is valid (between 1 and n inclusive).

JAVA
// Wrong: max-heap keeps the largest, not the kth largest
PriorityQueue<BigDecimal> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
// After n elements, peek() gives the 1st largest, not the kth

// Correct: min-heap of size k
PriorityQueue<BigDecimal> minHeap = new PriorityQueue<>(k);

How does this extend to top-k elements?

After the main loop, the heap contains exactly the k largest elements. You can drain the heap to get all of them, though they come out in min-to-max order. If you need them in descending order, drain into a list and reverse.

What should a senior engineer know?

A senior engineer chooses the min-heap for stream processing and bounded memory, and Quickselect for in-memory data when expected-linear time matters. The senior also knows about the median-of-medians variant that guarantees worst-case O(n) for Quickselect, though its constant factor is too high for practical use. For the banking dashboard, the stream-friendly min-heap is the right choice because transaction feeds are unbounded.

Visual Overview

DIAGRAM
  k = 3
  amounts: 15000  42500  8000  67200  3100  91000  28400

  Step-by-step heap state (min-heap, size capped at k=3):
  ┌────────────────────────────────────────────────────┐
  │  offer 15000  → heap: [15000]                      │
  │  offer 42500  → heap: [15000, 42500]               │
  │  offer 8000   → heap: [8000, 42500, 15000]         │
  │  size=3=k, no poll                                 │
  │  offer 67200  → size>k → poll 8000                 │
  │    heap: [15000, 42500, 67200]                     │
  │  offer 3100   → 3100 < peek(15000) → poll 3100     │
  │    heap: [15000, 42500, 67200]                     │
  │  offer 91000  → size>k → poll 15000                │
  │    heap: [42500, 67200, 91000]                     │
  │  offer 28400  → 28400 < peek(42500) → poll 28400   │
  │    heap: [42500, 67200, 91000]                     │
  └────────────────────────────────────────────────────┘
  peek() = 42500.75 (the 3rd largest)

Key Takeaway

The kth largest element is the value at position k from the top when the array is sorted descending. The brute-force way is to sort and index, but the min-heap approach is better when k is small or the data is a stream. I maintain a min-heap of size k so the root is always the smallest of the k largest seen so far, and after processing all elements the root is the kth largest overall.

In Java the heap is a PriorityQueue, which is a min-heap by default. I offer every element, and if the heap exceeds size k, I poll to discard the smallest. The time is O(n log k) with O(k) space, and it works on streams because I never hold more than k elements.

In my banking project at National Bank of India, I used this in a reporting dashboard that surfaces the top few largest transactions from a high-volume stream without storing the entire feed.

The senior-level trade-off is between the min-heap and Quickselect. The heap is deterministic, stream-friendly, and O(n log k). Quickselect is expected-linear but has a quadratic worst case and cannot stream.

Remember one sentence: a size-k min-heap discards everything too small and its root is the kth largest.

---

Share
Use ← → arrow keys to navigate