THN Interview Prep

703. Kth Largest Element in a Stream

At a Glance

  • Topic: heap-priority-queue
  • Pattern: Top-K / Heap
  • Difficulty: Easy
  • Companies: Amazon, Google, Meta, Microsoft, Bloomberg
  • Frequency: High
  • LeetCode: 703

Problem (one-liner)

After construction with an integer k and an initial array, each add(val) appends a value and returns the current kth largest element among all numbers seen so far. Output is always the kth largest in the maintained multiset.

Recognition Cues

  • "kth largest" in a streaming setting with repeated inserts
  • Need only the kth largest, not full sorting
  • Heap size should stay bounded by k, not grow with stream length

Diagram

At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.

Loading diagram…

Approaches

  • Brute force — sort entire collection after each addO(n log n) per add / O(n) space — too slow for many adds.
  • Better — maintain sorted structure or multiset — still heavy per update.
  • Optimal — min-heap of size exactly k holding the k largest values seen; heap root is the kth largest — O(log k) per add, O(k) space.

Optimal Solution

Go

import (
	"container/heap"
)

type IntMinHeap []int

func (heapSlice IntMinHeap) Len() int           { return len(heapSlice) }
func (heapSlice IntMinHeap) Less(left, right int) bool {
	return heapSlice[left] < heapSlice[right]
}
func (heapSlice IntMinHeap) Swap(left, right int) {
	heapSlice[left], heapSlice[right] = heapSlice[right], heapSlice[left]
}

func (heapSlice *IntMinHeap) Push(value any) {
	*heapSlice = append(*heapSlice, value.(int))
}

func (heapSlice *IntMinHeap) Pop() any {
	old := *heapSlice
	length := len(old)
	value := old[length-1]
	*heapSlice = old[0 : length-1]
	return value
}

type KthLargest struct {
	threshold int
	minHeap   *IntMinHeap
}

func Constructor(k int, nums []int) KthLargest {
	minHeap := &IntMinHeap{}
	heap.Init(minHeap)
	for _, num := range nums {
		heap.Push(minHeap, num)
		for minHeap.Len() > k {
			heap.Pop(minHeap)
		}
	}
	return KthLargest{threshold: k, minHeap: minHeap}
}

func (receiver *KthLargest) Add(value int) int {
	heap.Push(receiver.minHeap, value)
	for receiver.minHeap.Len() > receiver.threshold {
		heap.Pop(receiver.minHeap)
	}
	return (*receiver.minHeap)[0]
}

JavaScript

class MinHeap {
  constructor() {
    this.data = [];
  }

  size() {
    return this.data.length;
  }

  peek() {
    return this.data[0];
  }

  push(value) {
    this.data.push(value);
    this.bubbleUp(this.data.length - 1);
  }

  pop() {
    if (this.data.length === 0) {
      return undefined;
    }
    const top = this.data[0];
    const last = this.data.pop();
    if (this.data.length > 0) {
      this.data[0] = last;
      this.bubbleDown(0);
    }
    return top;
  }

  bubbleUp(index) {
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      if (this.data[index] >= this.data[parentIndex]) {
        break;
      }
      this.swap(index, parentIndex);
      index = parentIndex;
    }
  }

  bubbleDown(index) {
    const length = this.data.length;
    while (true) {
      const leftChild = index * 2 + 1;
      const rightChild = index * 2 + 2;
      let smallest = index;
      if (leftChild < length && this.data[leftChild] < this.data[smallest]) {
        smallest = leftChild;
      }
      if (rightChild < length && this.data[rightChild] < this.data[smallest]) {
        smallest = rightChild;
      }
      if (smallest === index) {
        break;
      }
      this.swap(index, smallest);
      index = smallest;
    }
  }

  swap(leftIndex, rightIndex) {
    const temp = this.data[leftIndex];
    this.data[leftIndex] = this.data[rightIndex];
    this.data[rightIndex] = temp;
  }
}

class KthLargest {
  constructor(k, nums) {
    this.threshold = k;
    this.minHeap = new MinHeap();
    for (const num of nums) {
      this.minHeap.push(num);
      while (this.minHeap.size() > k) {
        this.minHeap.pop();
      }
    }
  }

  add(value) {
    this.minHeap.push(value);
    while (this.minHeap.size() > this.threshold) {
      this.minHeap.pop();
    }
    return this.minHeap.peek();
  }
}

Walkthrough

k = 3, nums = [4, 5, 8, 2]. Build min-heap, keep size 3: largest three are 8, 5, 4 → min-heap root = 4 (3rd largest).

stepactionk largest (concept)min-heap rootreturn
after initamong 2,4,5,8 keep top 38, 5, 44
add(5)add 5; still top three 8,5,58, 5, 555

Invariant: Min-heap of size at most k contains exactly the k largest stream values; root is the kth largest.

Edge Cases

  • k === 1: heap holds one element; every add replaces tracking of maximum behavior via heap of size 1
  • Initial array shorter than k: heap has fewer than k elements until enough adds; problem guarantees stream fills—check problem statement for API contract (often initial length can be less than k)
  • Duplicate values: heap handles duplicates as separate entries

Pitfalls

  • Using a max-heap of all elements — blows memory and time
  • Popping wrong element — must pop from min-heap when maintaining k largest
  • Off-by-one on heap size after each add

Similar Problems

Fix similar problems - 973 is in same heap folder per user table.

Variants

  • Kth smallest in stream → max-heap of size k
  • Sliding window kth largest → deque / two heaps (harder)

Mind-Map Tags

#heap #min-heap #top-k #streaming #kth-largest #container-heap

Last updated on

Spotted something unclear or wrong on this page?

On this page