Problem overview
The method KClosest receives points, an int[][] where each point is {x, y}, and an int k. It returns the k points closest to the origin, closest first. Points at the same distance are ordered by x, and points with the same x by y. The answer is guaranteed to be unique: every point left out is farther away than every point in the answer.
For {{3,3},{5,-1},{-2,4}} with k = 2, the squared distances are 18, 26 and 20, so the answer is [[3,3],[-2,4]]. For {{0,2},{2,0},{1,1},{0,-2},{3,3}} with k = 4, the point {1,1} is closest, and {0,2}, {2,0} and {0,-2} are all the same distance away, so they come in order of x: [[1,1],[0,-2],[0,2],[2,0]].
Approach: keep only the k best so far
Sorting every point by distance works and costs O(n log n). The lesson asks for O(n log k) time and O(k) extra space, which is better when k is much smaller than n.
The idea is a max-heap of size k. A heap is a structure that gives quick access to its top element and lets you remove it. In a max-heap the top is the biggest. Go through the points one by one and put each into the heap. If the heap then holds more than k points, remove the top, which is the farthest point in the heap. That point can't be among the k closest, because k other points in the heap are at least as close. After the last point, the heap holds exactly the k closest. Each insertion and removal costs O(log k), because the heap never holds more than k + 1 points.
No square roots are needed. One point is closer than another exactly when its x * x + y * y is smaller, and with coordinates up to 10,000 in size that sum is at most 200,000,000, which fits in an int.
A heap is only partly ordered, so after emptying it into a list the code sorts those k entries by distance, then x, then y. That costs O(k log k), which is within the bound.
Step by step
- Create the heap. It is a
PriorityQueue<int[], int>namedheap: the element is an entry{dist, x, y}and the priority is the distance. C#'sPriorityQueueis a min-heap by default, so the code passesComparer<int>.Create((a, b) => b - a), which reverses the order and puts the biggest distance on top. - Loop over the points with
foreach (int[] point in points). Each point gets one chance to enter the heap. - Read the coordinates:
x = point[0]andy = point[1]. - Compute the squared distance
dist = x * x + y * y. - Add the entry with
heap.Enqueue(new int[]{dist, x, y}, dist). The array keeps the point together with its distance, and the second argument is the priority. - Trim: if
heap.Count > k, callheap.Dequeue(). It removes the farthest entry, so the heap goes back tokentries. - Empty the heap into a list. Declare
closestas aList<int[]>and, whileheap.Count > 0, addheap.Dequeue()to it. The entries come out farthest first. - Sort the list with
closest.Sortand a comparison ofaandb: compare the distances (index 0) if they differ, otherwise the x values (index 1) if they differ, otherwise the y values (index 2). - Build the result. Create
int[][] result = new int[k][]and, forifrom 0 tok - 1, setresult[i]tonew int[]{closest[i][1], closest[i][2]}. The answer holds only coordinates, so the distance at index 0 is dropped. - Return
result.
The solution in C#
class Solution {
public int[][] KClosest(int[][] points, int k) {
PriorityQueue<int[], int> heap = new PriorityQueue<int[], int>(Comparer<int>.Create((a, b) => b - a));
foreach (int[] point in points) {
int x = point[0], y = point[1];
int dist = x * x + y * y;
heap.Enqueue(new int[]{dist, x, y}, dist);
if (heap.Count > k) {
heap.Dequeue();
}
}
List<int[]> closest = new List<int[]>();
while (heap.Count > 0) {
closest.Add(heap.Dequeue());
}
closest.Sort((a, b) => a[0] != b[0] ? a[0] - b[0] : a[1] != b[1] ? a[1] - b[1] : a[2] - b[2]);
int[][] result = new int[k][];
for (int i = 0; i < k; i++) {
result[i] = new int[]{closest[i][1], closest[i][2]};
}
return result;
}
}
The C# details worth knowing:
PriorityQueue<TElement, TPriority>is a min-heap. It exists in .NET 6 and later, andDequeue()returns the element with the smallest priority. Enqueueing"a"with priority 5,"b"with 1 and"c"with 3 dequeuesb,c,a. With the reversed comparer it dequeuesa,c,b.- The element and the priority are separate. Here the element is the array
{dist, x, y}and the priority isdist. The heap only compares priorities, which is why the distance is stored twice. - The comparer
b - ais a shortcut that is safe only because both values are small. For values near the limits ofint, the subtraction can overflow and give the wrong sign.b.CompareTo(a)is the general form. - The sort comparison uses the same subtraction trick on distances up to 200,000,000 and coordinates of at most 10,000 in size, so it can't overflow here.
List<T>.Sortaccepts a lambda that returns a negative number, zero or a positive number. - Jagged arrays.
new int[k][]createskempty row slots, eachnulluntil the loop fills it. - Ties in the heap. The heap compares only distances and doesn't keep equal ones in any particular order. The uniqueness guarantee means a tie never decides which points stay.
- Squares stay in range. A point at
{10000, 10000}gives 200,000,000, far below 2,147,483,647.intarithmetic wraps silently on overflow, so with much larger coordinates the sums would have to belong.
Complexity
Time: O(n log k). For each of the n points there is one Enqueue and at most one Dequeue on a heap of at most k + 1 entries, each costing O(log k). Emptying the heap and sorting the k survivors adds O(k log k), which is no bigger.
Space: O(k). The heap holds at most k + 1 entries, and the list and the result hold k each.
Edge cases and common mistakes
kequals the number of points. Nothing is ever removed, and the result is all the points, sorted.- Several points at the same distance. The tie-break by
x, theny, comes from the final sort, not from the heap. - The same point twice.
{{1,1},{1,1},{2,2}}withk = 2returns[[1,1],[1,1]]. The heap holds both copies. - Negative coordinates. They are squared, so
{-2,2}and{2,-2}are the same distance away. - Using a min-heap. The top would be the closest point, not the farthest, so the code would remove the wrong points.
- Taking a square root. It is unnecessary, and comparing floating-point distances risks wrong ties.
- Returning the heap's entries directly. They are
{dist, x, y}, with the distance first, and they are not sorted.
Testing
{{1,3},{-2,2}}withk = 1gives[[-2,2]].{{3,3},{5,-1},{-2,4}}withk = 2gives[[3,3],[-2,4]].{{0,2},{2,0},{1,1},{0,-2},{3,3}}withk = 4gives[[1,1],[0,-2],[0,2],[2,0]].{{1,1},{1,1},{2,2}}withk = 2gives[[1,1],[1,1]].{{10000,10000},{-10000,-10000}}withk = 2gives[[-10000,-10000],[10000,10000]].
