Start learning free

LESSON 282 OF 409 · DIVIDE AND CONQUER

Smallest K Integers III in C++ with O(n) Time Complexity

Practice O(n) Read the full explanation

Given an array of positive integers nums, return the smallest k values, in any order you want.

Example:

Input: nums = [5, 9, 3, 6, 2, 1, 3, 2, 7, 5], k = 4
Output: [1, 2, 2, 3]
Explanation: Smallest number is 1, 2nd smallest is 2, 
            3rd smallest is 2, 4th smallest is 3
The result can be in any order, [2, 1, 3, 2] is also a correct answer.

Note:

Your algorithm should run in O(n) time on average and use O(log n) extra space on average (for the recursion), when partitioning in place.


STEP 1 OF 14
    Ctrl + Enter to check

    C++ · DIVIDE AND CONQUER

    Smallest K in C++, explained

    Understanding the Problem

    The core challenge of this problem is to find the smallest k integers from an array of positive integers efficiently. This problem is significant in scenarios where we need to quickly identify the smallest elements, such as in streaming data or large datasets where sorting the entire array is not feasible.

    Potential pitfalls include misunderstanding the requirement for O(n) time complexity and O(log n) extra space, which rules out simple sorting algorithms that typically run in O(n log n) time.

    Approach

    To solve this problem, we need to think about efficient ways to find the smallest k elements without sorting the entire array. A naive solution would be to sort the array and then pick the first k elements, but this would not meet the time complexity requirement.

    Instead, we can use a min-heap (priority queue) to keep track of the smallest k elements as we iterate through the array. This approach ensures that we only use O(log n) extra space and achieve O(n) time complexity.

    Naive Solution

    The naive solution involves sorting the array and then selecting the first k elements:

    // Naive solution: O(n log n) time complexity
    std::vector<int> smallestK(std::vector<int> nums, int k) {
        std::sort(nums.begin(), nums.end());
        return std::vector<int>(nums.begin(), nums.begin() + k);
    }
    

    While this solution is simple, it does not meet the O(n) time complexity requirement.

    Optimized Solution

    To achieve O(n) time complexity, we can use a min-heap to keep track of the smallest k elements:

    #include <vector>
    #include <queue>
    #include <algorithm>
    
    std::vector<int> smallestK(std::vector<int> nums, int k) {
        // Min-heap to store the smallest k elements
        std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
        
        // Add all elements to the min-heap
        for (int num : nums) {
            minHeap.push(num);
        }
        
        // Extract the smallest k elements
        std::vector<int> result;
        for (int i = 0; i < k; ++i) {
            result.push_back(minHeap.top());
            minHeap.pop();
        }
        
        return result;
    }
    

    This solution ensures that we only use O(log n) extra space for the heap operations and achieve O(n) time complexity by iterating through the array once.

    Algorithm

    Here is a step-by-step breakdown of the optimized algorithm:

    1. Initialize a min-heap (priority queue) to store the smallest k elements.
    2. Iterate through the array and add each element to the min-heap.
    3. Extract the smallest k elements from the min-heap and store them in the result vector.
    4. Return the result vector.

    Code Implementation

    #include <vector>
    #include <queue>
    #include <algorithm>
    
    std::vector<int> smallestK(std::vector<int> nums, int k) {
        // Min-heap to store the smallest k elements
        std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
        
        // Add all elements to the min-heap
        for (int num : nums) {
            minHeap.push(num);
        }
        
        // Extract the smallest k elements
        std::vector<int> result;
        for (int i = 0; i < k; ++i) {
            result.push_back(minHeap.top());
            minHeap.pop();
        }
        
        return result;
    }
    

    Complexity Analysis

    The time complexity of this approach is O(n) because we iterate through the array once. The space complexity is O(log n) due to the min-heap operations.

    Edge Cases

    Potential edge cases include:

    • k is greater than the size of the array: In this case, we should return the entire array.
    • k is zero: We should return an empty array.
    • Array contains duplicate elements: The algorithm should handle duplicates correctly.

    Testing

    To test the solution comprehensively, we should include a variety of test cases:

    #include <iostream>
    
    int main() {
        std::vector<int> nums = {5, 9, 3, 6, 2, 1, 3, 2, 7, 5};
        int k = 4;
        std::vector<int> result = smallestK(nums, k);
        
        for (int num : result) {
            std::cout << num << " ";
        }
        std::cout << std::endl;
        
        return 0;
    }
    

    Thinking and Problem-Solving Tips

    When approaching such problems, it is essential to:

    • Understand the constraints and requirements clearly.
    • Think about efficient data structures that can help achieve the desired time complexity.
    • Break down the problem into smaller steps and solve each step methodically.

    Conclusion

    In this blog post, we discussed how to find the smallest k integers from an array efficiently using a min-heap. We covered the problem definition, approach, algorithm, code implementation, complexity analysis, edge cases, and testing. Understanding and solving such problems is crucial for improving problem-solving skills and preparing for coding interviews.

    Additional Resources

    For further reading and practice problems, consider the following resources:

    Keep going with C++About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free