Kth Largest Element in a Stream
Kth Largest Element in a Stream: Design and Implementation
The Kth Largest Element in a Stream problem requires designing a system to dynamically track the k-th largest integer in a stream of numbers.
This task demands an efficient approach to handle frequent updates while maintaining the k-th largest element in near real-time.
Problem:
Design a class to find the kth largest integer in a stream of values, including duplicates. E.g. the 2nd largest from [1, 2, 3, 3] is 3. The stream is not necessarily sorted.
Implement the following methods:
- constructor(int k, int[] nums) Initializes the object given an integer k and the stream of integers nums.
- int add(int val) Adds the integer val to the stream and returns the kth largest integer in the stream.
Problem Breakdown
Task
Initialization:
- Create a class KthLargest initialized with:
- An integer k representing the position of the k-th largest element.
- An initial array nums that represents the stream of integers.
- Create a class KthLargest initialized with:
Dynamic Updates:
- A method add(val) adds a new value to the stream and returns the k-th largest element in the updated stream.
Explanation:
KthLargest kthLargest = new KthLargest(3, [1, 2, 3, 3]);
kthLargest.add(3); // return 3
kthLargest.add(5); // return 3
kthLargest.add(6); // return 3
kthLargest.add(7); // return 5
kthLargest.add(8); // return 6
Constraints:
- 1 <= k <= 1000
- 0 <= nums.length <= 1000
- -1000 <= nums[i] <= 1000
- -1000 <= val <= 1000
- There will always be at least k integers in the stream when you search for the kth integer.
Approach
Key Idea: Min-Heap
To efficiently track the k-th largest element, we use a min-heap of size k
.
Why Min-Heap?
- A min-heap keeps the smallest element at the root.
- By maintaining a heap of size
k
, the root always represents the k-th largest element, as it ensures the heap contains the largestk
elements of the stream.
Algorithm
Initialization
- Create a min-heap with the first
k
elements ofnums
. - If
nums
has more thank
elements, iterate through the remaining numbers and adjust the heap to maintain only the largestk
elements.
Add Method
- Add the new element
val
to the heap. - If the heap exceeds size
k
, remove the smallest element (heap root). - Return the root of the heap, which is the k-th largest element.
There are mainly two approach to solve this problem –
- Brute Force
- Min Heap
1. Sorting
- Time complexity: O(m∗nlogn)
- Space complexity: O(1) or O(n)depending on the sorting algorithm.
class KthLargest: def __init__(self, k: int, nums: List[int]): self.k = k self.arr = nums def add(self, val: int) -> int: self.arr.append(val) self.arr.sort() return self.arr[len(self.arr) - self.k]
class KthLargest { public: vectorarr; int k; KthLargest(int k, vector & nums) { this->arr = nums; this->k = k; } int add(int val) { arr.push_back(val); sort(arr.begin(), arr.end()); return arr[arr.size() - k]; } };
class KthLargest { Listarr; int K; public KthLargest(int k, int[] nums) { K = k; arr = new ArrayList(); for (int i = 0; i < nums.length; i++) { arr.add(nums[i]); } } public int add(int val) { arr.add(val); Collections.sort(arr); return arr.get(arr.size() - K); } }
class KthLargest { /** * @param {number} k * @param {number[]} nums */ constructor(k, nums) { this.arr = nums; this.k = k; } /** * @param {number} val * @return {number} */ add(val) { this.arr.push(val); this.arr.sort((a, b) => a - b); return this.arr[this.arr.length - this.k]; } }
2. Min -Heap
Time & Space Complexity
- Time complexity: O(m∗logk)
- Space complexity: O(k)
class KthLargest { private: priority_queue, greater > minHeap; int k; public: KthLargest(int k, vector & nums) { this->k = k; for (int num : nums) { minHeap.push(num); if (minHeap.size() > k) { minHeap.pop(); } } } int add(int val) { minHeap.push(val); if (minHeap.size() > k) { minHeap.pop(); } return minHeap.top(); } };
class KthLargest { private PriorityQueueminHeap; private int k; public KthLargest(int k, int[] nums) { this.k = k; this.minHeap = new PriorityQueue<>(); for (int num : nums) { minHeap.offer(num); if (minHeap.size() > k) { minHeap.poll(); } } } public int add(int val) { minHeap.offer(val); if (minHeap.size() > k) { minHeap.poll(); } return minHeap.peek(); } }
class KthLargest: def __init__(self, k: int, nums: List[int]): self.minHeap, self.k = nums, k heapq.heapify(self.minHeap) while len(self.minHeap) > k: heapq.heappop(self.minHeap) def add(self, val: int) -> int: heapq.heappush(self.minHeap, val) if len(self.minHeap) > self.k: heapq.heappop(self.minHeap) return self.minHeap[0]
/** * const { MinPriorityQueue } = require('@datastructures-js/priority-queue'); */ class KthLargest { /** * @param {number} k * @param {number[]} nums */ constructor(k, nums) { this.minHeap = new MinPriorityQueue(); this.k = k; for (const num of nums) { this.minHeap.enqueue(num); } while (this.minHeap.size() > k) { this.minHeap.dequeue(); } } /** * @param {number} val * @return {number} */ add(val) { this.minHeap.enqueue(val); if (this.minHeap.size() > this.k) { this.minHeap.dequeue(); } return this.minHeap.front(); } }
Where mm is the number of calls made to add().