Problem

Given an array of n numbers (possibly very large, e.g., a million elements), find the k smallest numbers in the array. Return them in any order.

Examples

Example 1

1
2
3
Input: arr = [7, 10, 4, 3, 20, 15], k = 3
Output: [3, 4, 7]
Explanation: The three smallest numbers are 3, 4, and 7.

Example 2

1
2
3
Input: arr = [1, 23, 12, 9, 30, 2, 50], k = 4
Output: [1, 2, 9, 12]
Explanation: The four smallest numbers are 1, 2, 9, and 12.

Solution

Method 1 – Sorting

Intuition

Sort the array and return the first k elements. Simple but not optimal for very large arrays.

Approach

  • Sort the array in ascending order.
  • Return the first k elements.

Complexity

  • ⏰ Time complexity: O(n log n), due to sorting.
  • 🧺 Space complexity: O(1) (in-place sort).

Method 2 – Max Heap (Optimal for Large n, Small k)

Intuition

Maintain a max heap of size k. Iterate through the array, keeping only the k smallest elements seen so far.

Approach

  • Build a max heap with the first k elements.
  • For each remaining element, if it is smaller than the heap’s maximum, replace the maximum and re-heapify.
  • At the end, the heap contains the k smallest elements.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    vector<int> kSmallest(vector<int>& arr, int k) {
        priority_queue<int> maxHeap;
        for (int i = 0; i < arr.size(); ++i) {
            if (maxHeap.size() < k) maxHeap.push(arr[i]);
            else if (arr[i] < maxHeap.top()) {
                maxHeap.pop();
                maxHeap.push(arr[i]);
            }
        }
        vector<int> ans;
        while (!maxHeap.empty()) {
            ans.push_back(maxHeap.top());
            maxHeap.pop();
        }
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import "container/heap"

type IntHeap []int
func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // max-heap
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[:n-1]
    return x
}

func KSmallest(arr []int, k int) []int {
    h := &IntHeap{}
    heap.Init(h)
    for _, v := range arr {
        if h.Len() < k {
            heap.Push(h, v)
        } else if v < (*h)[0] {
            heap.Pop(h)
            heap.Push(h, v)
        }
    }
    ans := make([]int, h.Len())
    for i := range ans {
        ans[i] = heap.Pop(h).(int)
    }
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution {
    public int[] kSmallest(int[] arr, int k) {
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        for (int num : arr) {
            if (maxHeap.size() < k) maxHeap.offer(num);
            else if (num < maxHeap.peek()) {
                maxHeap.poll();
                maxHeap.offer(num);
            }
        }
        int[] ans = new int[maxHeap.size()];
        int i = 0;
        while (!maxHeap.isEmpty()) ans[i++] = maxHeap.poll();
        return ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import heapq
class Solution:
    def k_smallest(self, arr: list[int], k: int) -> list[int]:
        if k == 0: return []
        heap = [-x for x in arr[:k]]
        heapq.heapify(heap)
        for x in arr[k:]:
            if -heap[0] > x:
                heapq.heappop(heap)
                heapq.heappush(heap, -x)
        return sorted([-x for x in heap])

Complexity

  • ⏰ Time complexity: O(n log k), as each insertion/removal in the heap of size k is O(log k).
  • 🧺 Space complexity: O(k), for the heap.

Method 3 – Quickselect (Hoare’s Algorithm)

Intuition

Quickselect partitions the array so that the k smallest elements are in the first k positions (not necessarily sorted). Efficient for average case.

Approach

  • Use the partitioning logic of quicksort to recursively find the kth smallest element.
  • After partitioning, the first k elements are the k smallest.

Complexity

  • ⏰ Time complexity: O(n) average, O(n^2) worst case.
  • 🧺 Space complexity: O(1) (in-place).