Loading problems...
TL;DR: We utilize a Max-Heap of size K to maintain the K smallest distances found so far, efficiently discarding the farthest points as we iterate through the input.
The problem asks us to find the k points from a given list of (x, y) coordinates that are closest to the origin (0, 0). The distance is measured using Euclidean distance. While the output can be in any order, the set of points returned must be the unique k closest points. This is a classic example of a "Top K" problem, making the LeetCode 973 challenge a staple in technical interviews.
The most straightforward way to solve K Closest Points to Origin is to calculate the distance for every point and then sort the entire list based on these distances.
Naive Algorithm:
k points from the sorted list.python# Pseudo-code for Brute Force def kClosest(points, k): # Calculate squared distance for all points points_with_dist = [] for x, y in points: dist = x*x + y*y points_with_dist.append((dist, [x, y])) # Sort entire array - O(N log N) points_with_dist.sort(key=lambda p: p[0]) # Return top k return [p[1] for p in points_with_dist[:k]]
Complexity Analysis: The time complexity is dominated by the sorting step, which is , where is the number of points. The space complexity is to store the distances.
Why it fails (or is suboptimal): While is acceptable for smaller constraints, it performs unnecessary work. We sort the entire array of points, even though we only care about the top . If is 10 million and is 5, sorting the entire dataset is extremely wasteful. The optimal solution should scale better with when is small.
The core insight that optimizes the brute force approach is realizing we only need to maintain the "best" K candidates at any given time. We don't need to know the relative order of the points that are not in the top K.
To efficiently maintain the K smallest elements, we can use a Max-Heap of size K.
Why a Max-Heap for finding the smallest elements?
This is often counter-intuitive. To keep the smallest distances, we need to be able to identify and remove the largest distance among our currently selected K points.
K points, the root of a Max-Heap gives us the point with the largest distance in that group (the "worst" of the "best").K. We pop the root (remove the farthest) and push the new point.K points currently in the heap, so we discard it.Visual Description:
Imagine a bucket that can only hold K balls. We assign a "size" to each ball based on its distance from the origin. We want the bucket to hold the K smallest balls. As we pick up new balls from a pile, we look at the bucket. If the bucket isn't full, we toss the ball in. If the bucket is full, we look for the largest ball inside it. If the new ball is smaller than the largest ball in the bucket, we swap them. The Max-Heap acts as this bucket, keeping the largest ball at the top for easy access and removal.

Let's trace the algorithm with points = [[1,3], [-2,2]] and k = 1.
heap = [].[(10, [1, 3])].[(10, [1, 3]), (8, [-2, 2])].[[-2, 2]]. Return this list.We maintain an invariant: after processing the -th element, the heap contains the points with the smallest distances among the first points.
Base Case: For the first elements, the heap simply stores all of them. The invariant holds trivially. Inductive Step: Suppose the heap holds the closest points among the first elements. Let be the largest distance in the heap (the root). Consider the -th element with distance .
Thus, after steps, the heap contains the global closest points.
In C++, std::priority_queue is a Max-Heap by default. We store pairs of {squared_distance, index} or the point vector itself.
cpp#include <vector> #include <queue> using namespace std; class Solution { public: vector<vector<int>> kClosest(vector<vector<int>>& points, int k) { // Max-Heap to store pairs of (distance_squared, point_coordinates) // By default, priority_queue sorts by the first element of the pair descending priority_queue<pair<long, vector<int>>> maxHeap; for (const auto& point : points) { long x = point[0]; long y = point[1]; long dist = x * x + y * y; maxHeap.push({dist, point}); // If heap size exceeds k, remove the farthest point (largest distance) if (maxHeap.size() > k) { maxHeap.pop(); } } vector<vector<int>> result; while (!maxHeap.empty()) { result.push_back(maxHeap.top().second); maxHeap.pop(); } return result; } };
In Java, PriorityQueue is a Min-Heap by default. To create a Max-Heap, we provide a custom comparator that reverses the natural ordering (b - a).
javaimport java.util.PriorityQueue; class Solution { public int[][] kClosest(int[][] points, int k) { // Max-Heap: orders points by distance in descending order // We store indices or the points themselves. Here we store the point arrays. PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> { long distA = (long) a[0] * a[0] + (long) a[1] * a[1]; long distB = (long) b[0] * b[0] + (long) b[1] * b[1]; return Long.compare(distB, distA); // Descending order }); for (int[] point : points) { maxHeap.offer(point); // If we have more than k elements, remove the one with the largest distance if (maxHeap.size() > k) { maxHeap.poll(); } } int[][] result = new int[k][2]; for (int i = 0; i < k; i++) { result[i] = maxHeap.poll(); } return result; } }
Python's heapq module implements a Min-Heap. To simulate a Max-Heap, we store the negative of the distance. The "largest" distance becomes the "smallest" negative number, sitting at the root of the Min-Heap.
pythonimport heapq class Solution: def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]: # Max-Heap simulated using Min-Heap with negative values heap = [] for x, y in points: dist = -(x*x + y*y) # Negate distance for Max-Heap behavior heapq.heappush(heap, (dist, [x, y])) # If heap grows beyond k, remove the smallest item # (which corresponds to the largest actual distance) if len(heap) > k: heapq.heappop(heap) # Extract points from the heap return [point for (_, point) in heap]
Time Complexity:
Space Complexity:
The Heap - Top K Elements pattern is highly reusable. Understanding how to maintain a bounded heap allows you to solve several other popular interview questions:
In all these cases, the logic remains the same: use a heap to maintain a "window" of the top candidates while iterating through the data.
Why does my naive solution get Time Limit Exceeded (TLE)?
(10, [1, 3]).[(8, [-2, 2])].Why calculate square roots?
Math.sqrt() or pow(..., 0.5) during distance comparison.Why use a Max-Heap instead of a Min-Heap?
K to find the closest points.Why heapify the whole array?
points list into a heap first () and then popping times ().