k largest elements

Find the K Largest Elements in an Array

Finding the k largest elements in an array is a classic algorithmic problem frequently asked in coding interviews. It tests your knowledge of data structures, specifically priority queues and heaps. In this comprehensive guide, we will explore the optimal C++ solution to extract the k largest elements in decreasing order.

Understanding the K Largest Elements Problem

Given an array of positive integers and an integer k, the objective is to return the k largest elements from the array, sorted in decreasing order. While a naive approach might involve sorting the entire array in O(N log N) time, we can optimize this significantly using a min-heap, reducing the time complexity to O(N log k).

Example Scenarios

Input: arr[] = [12, 5, 787, 1, 23], k = 2
Output: [787, 23]
Explanation: The first largest element in the array is 787 and the second largest is 23.
Input: arr[] = [1, 23, 12, 9, 30, 2, 50], k = 3 
Output: [50, 30, 23]
Explanation: The three largest elements in the array are 50, 30, and 23.

Problem Constraints

  • 1 <= k <= arr.size() <= 10^6
  • 1 <= arr[i] <= 10^6

Optimal C++ Solution Using a Min-Heap

To efficiently track the k largest elements while iterating through the array, we utilize a min-heap (implemented via std::priority_queue in C++). A min-heap allows us to maintain the top k elements seen so far. If a new element is larger than the smallest element in our heap (the root), we replace the root with this new element.

class Solution {
    std::vector<int> minHeapToVector(std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap) {
        std::vector<int> result;
        while (!minHeap.empty()) {
            result.push_back(minHeap.top());
            minHeap.pop();
        }
        std::reverse(result.begin(), result.end());
        return result;
    }
    
  public:
    std::vector<int> kLargest(std::vector<int>& arr, int k) {
        std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
        
        for( int i : arr ) {
            if( minHeap.size() < k )
                minHeap.push(i);
            else if( minHeap.top() <= i ) {
                minHeap.pop();
                minHeap.push(i);
            }
        }
        
        return minHeapToVector(minHeap);
    }
};

Complexity Analysis

Using a min-heap guarantees optimal performance for large datasets. Inserting an element into a heap of size k takes O(log k) time. Since we iterate over all N elements in the array, the overall time complexity is O(N log k). The space complexity is O(k) because the priority queue stores at most k elements at any given time. This is highly efficient and satisfies the problem constraints seamlessly.

Frequently Asked Questions (FAQ)

Why use a min-heap instead of a max-heap for finding the largest elements?

A min-heap is preferred because we need to easily access and remove the smallest of the "k largest" elements to make room for larger candidates as we iterate through the array. A max-heap would keep the absolute largest element at the root, making it difficult to maintain the boundary of the top k elements.

Can this problem be solved using sorting?

Yes, you can simply sort the array in descending order and return the first k elements. However, the time complexity would be O(N log N), which is less efficient than the O(N log k) approach, especially when k is much smaller than N.

Is there a faster approach than using a heap?

Quickselect, based on the QuickSort algorithm, can find the k-th largest element in O(N) average time. However, in the worst case, its time complexity can degrade to O(N^2), and it may not yield the elements in sorted order without additional sorting steps.