Find K Smallest Elements

Find the k smallest elements from an unsorted array

Language Selection

Choose your preferred programming language

Showing: Python

Find K Smallest Elements

Problem Statement

Given an integer array nums and an integer k, return the k smallest elements from the array. The result can be in any order.

Input/Output Specifications

  • Input:
    • nums: Array of integers
    • k: Integer representing number of smallest elements to find (1 <= k <= nums.length)
  • Output: Array containing k smallest elements in any order

Constraints

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

Examples

Example 1:

Input: nums = [3,2,1,5,6,4], k = 2
Output: [1,2] or [2,1]
Explanation: The 2 smallest elements are 1 and 2.

Example 2:

Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
Output: [1,2,2,3] (in any order)
Explanation: The 4 smallest elements are 1, 2, 2, and 3.

Solution Approaches

Approach 1: Max-Heap (Priority Queue) - Optimal for Memory

Algorithm Explanation:

  1. Use a max-heap of size k to maintain the k smallest elements seen so far
  2. Iterate through all elements:
    • If heap size < k, add element
    • If heap size = k and current element < heap top, remove top and add current
  3. Return all elements in the heap

Implementation:

Python:

import heapq

def find_k_smallest_elements(nums, k):
    """
    Find k smallest elements using max-heap
    Time: O(n log k)
    Space: O(k)
    """
    if k == 0:
        return []
    
    max_heap = []
    
    for num in nums:
        if len(max_heap) < k:
            heapq.heappush(max_heap, -num)  # Negative for max-heap
        elif num < -max_heap[0]:  # num < largest in heap
            heapq.heapreplace(max_heap, -num)
    
    return [-x for x in max_heap]  # Convert back to positive

# Alternative implementation using heapq.nsmallest
def find_k_smallest_elements_builtin(nums, k):
    """
    Find k smallest elements using built-in function
    Time: O(n log k)
    Space: O(k)
    """
    return heapq.nsmallest(k, nums)

Java:

import java.util.*;

class Solution {
    /**
     * Find k smallest elements using max-heap
     * Time: O(n log k)
     * Space: O(k)
     */
    public int[] findKSmallestElements(int[] nums, int k) {
        if (k == 0) return new int[0];
        
        // Max-heap using reverse order comparator
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
        
        for (int num : nums) {
            if (maxHeap.size() < k) {
                maxHeap.offer(num);
            } else if (num < maxHeap.peek()) {
                maxHeap.poll();
                maxHeap.offer(num);
            }
        }
        
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = maxHeap.poll();
        }
        
        return result;
    }
}

Go:

import (
    "container/heap"
)

// MaxHeap for maintaining k smallest elements
type MaxHeap []int

func (h MaxHeap) Len() int           { return len(h) }
func (h MaxHeap) Less(i, j int) bool { return h[i] > h[j] } // Max-heap
func (h MaxHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *MaxHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *MaxHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

// findKSmallestElements - Find k smallest elements using max-heap
// Time: O(n log k), Space: O(k)
func findKSmallestElements(nums []int, k int) []int {
    if k == 0 {
        return []int{}
    }
    
    maxHeap := &MaxHeap{}
    heap.Init(maxHeap)
    
    for _, num := range nums {
        if maxHeap.Len() < k {
            heap.Push(maxHeap, num)
        } else if num < (*maxHeap)[0] {
            heap.Pop(maxHeap)
            heap.Push(maxHeap, num)
        }
    }
    
    result := make([]int, k)
    for i := 0; i < k; i++ {
        result[i] = heap.Pop(maxHeap).(int)
    }
    
    return result
}

JavaScript:

class MaxHeap {
    constructor() {
        this.heap = [];
    }
    
    push(val) {
        this.heap.push(val);
        this.bubbleUp();
    }
    
    pop() {
        if (this.heap.length === 0) return null;
        if (this.heap.length === 1) return this.heap.pop();
        
        const max = this.heap[0];
        this.heap[0] = this.heap.pop();
        this.bubbleDown();
        return max;
    }
    
    peek() {
        return this.heap[0];
    }
    
    size() {
        return this.heap.length;
    }
    
    bubbleUp() {
        let idx = this.heap.length - 1;
        while (idx > 0) {
            const parentIdx = Math.floor((idx - 1) / 2);
            if (this.heap[parentIdx] >= this.heap[idx]) break;
            [this.heap[parentIdx], this.heap[idx]] = [this.heap[idx], this.heap[parentIdx]];
            idx = parentIdx;
        }
    }
    
    bubbleDown() {
        let idx = 0;
        while (2 * idx + 1 < this.heap.length) {
            let maxChildIdx = 2 * idx + 1;
            if (2 * idx + 2 < this.heap.length && 
                this.heap[2 * idx + 2] > this.heap[maxChildIdx]) {
                maxChildIdx = 2 * idx + 2;
            }
            
            if (this.heap[idx] >= this.heap[maxChildIdx]) break;
            [this.heap[idx], this.heap[maxChildIdx]] = [this.heap[maxChildIdx], this.heap[idx]];
            idx = maxChildIdx;
        }
    }
}

/**
 * Find k smallest elements using max-heap
 * Time: O(n log k)
 * Space: O(k)
 */
function findKSmallestElements(nums, k) {
    if (k === 0) return [];
    
    const maxHeap = new MaxHeap();
    
    for (const num of nums) {
        if (maxHeap.size() < k) {
            maxHeap.push(num);
        } else if (num < maxHeap.peek()) {
            maxHeap.pop();
            maxHeap.push(num);
        }
    }
    
    const result = [];
    while (maxHeap.size() > 0) {
        result.push(maxHeap.pop());
    }
    
    return result;
}

C#:

using System;
using System.Collections.Generic;

public class Solution {
    /// <summary>
    /// Find k smallest elements using max-heap (PriorityQueue)
    /// Time: O(n log k)
    /// Space: O(k)
    /// </summary>
    public int[] FindKSmallestElements(int[] nums, int k) {
        if (k == 0) return new int[0];
        
        // Max-heap using negative priority (higher value = higher priority)
        var maxHeap = new PriorityQueue<int, int>(Comparer<int>.Create((a, b) => b.CompareTo(a)));
        
        foreach (int num in nums) {
            if (maxHeap.Count < k) {
                maxHeap.Enqueue(num, num);
            } else if (num < maxHeap.Peek()) {
                maxHeap.Dequeue();
                maxHeap.Enqueue(num, num);
            }
        }
        
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = maxHeap.Dequeue();
        }
        
        return result;
    }
}

Approach 2: Sorting (Simple but Less Efficient)

Algorithm Explanation:

  1. Sort the entire array
  2. Return first k elements

Implementation:

Python:

def find_k_smallest_sorting(nums, k):
    """
    Find k smallest elements using sorting
    Time: O(n log n)
    Space: O(1) if in-place sorting allowed
    """
    nums.sort()
    return nums[:k]

Approach 3: Quickselect with Modification (Optimal Average Time)

Algorithm Explanation:

  1. Use quickselect to find the kth smallest element
  2. Collect all elements <= kth smallest
  3. Return exactly k elements

Implementation:

Python:

import random

def find_k_smallest_quickselect(nums, k):
    """
    Find k smallest elements using quickselect
    Time: O(n) average, O(n²) worst
    Space: O(1)
    """
    def quickselect(arr, left, right, k):
        if left == right:
            return arr[left]
        
        pivot_index = random.randint(left, right)
        pivot_index = partition(arr, left, right, pivot_index)
        
        if k == pivot_index:
            return arr[k]
        elif k < pivot_index:
            return quickselect(arr, left, pivot_index - 1, k)
        else:
            return quickselect(arr, pivot_index + 1, right, k)
    
    def partition(arr, left, right, pivot_index):
        pivot_value = arr[pivot_index]
        arr[pivot_index], arr[right] = arr[right], arr[pivot_index]
        
        store_index = left
        for i in range(left, right):
            if arr[i] < pivot_value:  # For smallest elements
                arr[store_index], arr[i] = arr[i], arr[store_index]
                store_index += 1
        
        arr[right], arr[store_index] = arr[store_index], arr[right]
        return store_index
    
    if not nums or k <= 0:
        return []
    
    nums_copy = nums[:]
    kth_smallest = quickselect(nums_copy, 0, len(nums_copy) - 1, k - 1)
    
    result = []
    for num in nums:
        if num <= kth_smallest and len(result) < k:
            result.append(num)
    
    return result

Key Insights

  • Max-Heap for K Smallest: Use max-heap to track k smallest elements efficiently
  • Heap Size Management: Maintain exactly k elements in heap
  • Element Comparison: Compare new elements with heap root (largest of k smallest)
  • Memory Efficiency: Heap approach uses O(k) space vs O(n) for sorting

Edge Cases

  1. k = 0: Return empty array
  2. k = n: Return entire array
  3. Duplicate Elements: Handle multiple occurrences of kth smallest
  4. Single Element: Array with one element
  5. All Same Values: All elements are identical

Test Cases

def test_find_k_smallest_elements():
    # Basic case
    assert sorted(find_k_smallest_elements([3,2,1,5,6,4], 2)) == [1,2]
    
    # With duplicates
    assert sorted(find_k_smallest_elements([3,2,3,1,2,4,5,5,6], 4)) == [1,2,2,3]
    
    # k = 1
    assert find_k_smallest_elements([5], 1) == [5]
    
    # k = n
    result = find_k_smallest_elements([3,1,2], 3)
    assert sorted(result) == [1,2,3]
    
    # All same elements
    result = find_k_smallest_elements([5,5,5,5], 2)
    assert result == [5,5]
    
    # Negative numbers
    assert sorted(find_k_smallest_elements([-1,-3,2,0,1], 3)) == [-3,-1,0]
    
    # Already sorted
    assert find_k_smallest_elements([1,2,3,4,5], 3) == [1,2,3]
    
    print("All tests passed!")

test_find_k_smallest_elements()

Interview Tips

  1. Discuss Heap Choice: Explain why max-heap for k smallest elements
  2. Compare Approaches: Heap vs sorting vs quickselect trade-offs
  3. Handle Edge Cases: Empty input, k=0, k=n scenarios
  4. Space Optimization: Heap approach saves memory for large arrays
  5. Built-in Functions: Mention language-specific optimized functions

Follow-up Questions

  1. Kth Smallest Element: Return only the kth smallest value
  2. Streaming Data: Handle continuous input of numbers
  3. Find K Largest: Modify approach for largest elements
  4. Memory Constraints: What if only O(1) extra space allowed?
  5. Stability: Maintain relative order of equal elements

Common Mistakes

  1. Wrong Heap Type: Using min-heap instead of max-heap
  2. Boundary Conditions: Not handling k=0 or k=n properly
  3. Duplicate Elements: Incorrect handling of equal values
  4. Heap Size: Not maintaining exactly k elements
  5. Comparison Logic: Wrong condition for heap replacement

Concept Explanations

Max-Heap for K Smallest: This is the key insight - to find k smallest elements efficiently, we maintain a max-heap of size k. The largest element in this heap represents the “boundary” - any new element smaller than this boundary should replace it.

Heap vs Sorting Trade-off: While sorting gives O(n log n) time, the heap approach gives O(n log k). When k « n, this is significantly more efficient. Additionally, heap uses O(k) space compared to potential O(n) space for sorting.

When to Apply: Use this pattern for top-k problems where k is much smaller than n, streaming data scenarios, or when memory is constrained. The heap approach is particularly effective for finding k smallest/largest elements in large datasets.