K Closest Points to Origin
1. Problem & Core Objective
Given an array of points on a plane and an integer k, return the k points closest to the origin (0, 0). The answer may be returned in any order.
points = [[1,3],[-2,2]], k = 1 → [[-2,2]]
distances √10 and √8
points = [[3,3],[5,-1],[-2,4]], k = 2 → [[3,3],[-2,4]]Constraints: 1 <= k <= points.length <= 10^4 · -10^4 <= x, y <= 10^4
What's actually being tested: two things. That you skip the square root — comparing distances doesn't need it — and that you pick the right selection strategy. It's question 1's heap inversion applied to the k smallest, so the heap flips the other way.
2. First-Principles Thought Process
Don't compute the square root
Distance from the origin is √(x² + y²). But we only ever compare distances, never report them.
√ is monotonically increasing, so for non-negative a and b:
√a < √b if and only if a < bSo comparing x² + y² gives identical ordering. Dropping the root removes a floating-point operation per comparison and, more importantly, keeps everything in integers — no precision concerns, no ties that are equal in reality but differ in the last bit.
With |x|, |y| <= 10^4, the maximum squared distance is 2 × 10^8, comfortably inside int. Worth checking rather than assuming — this is exactly the bound that decides whether you need long.
Which heap, and why it flips
We want the k smallest distances. By the inversion from question 1:
- keep the
ksmallest in a heap of sizek - the one to evict when a closer point arrives is the largest of those kept
- so the root must be the maximum → max-heap
Min-heap for the k largest; max-heap for the k smallest. This is the mirror of question 1, and stating the rule both ways is the cleanest way to show it's understood rather than memorised.
The alternatives are genuinely competitive
Unlike question 1, this isn't a stream — the whole array is in memory. That opens two more options:
| Approach | Time | Space | When |
|---|---|---|---|
Sort by distance, take k | O(n log n) | O(1) extra | Simple; fine at n = 10^4 |
Max-heap of size k | O(n log k) | O(k) | k ≪ n, or streaming |
| Quickselect | O(n) average | O(1) | Single query, array in memory |
The heap wins when k is much smaller than n; quickselect wins on raw speed for one query. Being able to say which and why is the answer, not picking one.
3. Solution Paths
Approach 1 — Sort everything by distance (brute force, and perfectly reasonable)
public int[][] kClosest(int[][] points, int k) {
Arrays.sort(points, Comparator.comparingInt(p -> p[0] * p[0] + p[1] * p[1]));
return Arrays.copyOfRange(points, 0, k);
}- Time
O(n log n)· SpaceO(1)extra (sorts in place) —O(log n)for the sort's stack
Counter-questions on this approach
⭐ "Two lines and it's correct. What's the objection?"
It computes the full ordering of all
npoints to read the firstk. Ifk = 1I've sorted 10,000 points to find one.With a size-
kheap it'sO(n log k); with quickselect,O(n)average. Neither produces the wasted ordering.That said, at
n = 10^4this runs in about1.3 × 10^5comparisons — instant. I'd write it first and say it's what I'd ship unlessk ≪ nor the data streams.
⭐ "Is p[0] * p[0] + p[1] * p[1] safe from overflow?"
Yes, and it's worth confirming rather than assuming. With coordinates bounded by
10^4, each square is at most10^8and the sum at most2 × 10^8—intholds up to about2.1 × 10^9. Safe with an order of magnitude to spare.If coordinates could reach
10^5, the sum would be2 × 10^10and I'd needlong. That's the check to do before writing the comparator.
"Why comparingInt rather than (a, b) -> dist(a) - dist(b)?"
Subtraction overflows when the difference exceeds
intrange. Here distances are bounded so it happens to be safe, butcomparingIntis both safer and clearer. It also computes the key once per comparison rather than twice, though the JIT usually handles that.
"Does this mutate the input?"
Yes —
Arrays.sortsorts in place, so the caller's array is reordered. Worth flagging; if that matters, sort a copy of the indices instead.
Approach 2 — Max-heap of size k (optimal when k ≪ n)
public int[][] kClosest(int[][] points, int k) {
// max-heap on squared distance: the root is the FARTHEST of the k kept
PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
(a, b) -> (b[0]*b[0] + b[1]*b[1]) - (a[0]*a[0] + a[1]*a[1]));
for (int[] p : points) {
maxHeap.offer(p);
if (maxHeap.size() > k) maxHeap.poll(); // drop the farthest
}
int[][] result = new int[k][];
for (int i = 0; i < k; i++) result[i] = maxHeap.poll();
return result;
}Trace — [[3,3],[5,-1],[-2,4]], k = 2:
| Point | x² + y² | Heap after offer (by distance) | Size > 2? | Poll |
|---|---|---|---|---|
[3,3] | 18 | {18} | no | — |
[5,-1] | 26 | {26, 18} | no | — |
[-2,4] | 20 | {26, 20, 18} | yes | 26 → [5,-1] |
| — | — | {20, 18} | — | — |
Result: [-2,4] and [3,3] ✓
- Time
O(n log k)· SpaceO(k)
Counter-questions on this approach
⭐ "Why a max-heap when the question asks for the closest?"
Because I'm keeping the
ksmallest distances, and the element to evict when a closer point arrives is the largest of those kept. A max-heap puts exactly that at the root.It's the mirror of the previous stream question: there I kept the
klargest and needed a min-heap. The rule is min-heap for the k largest, max-heap for the k smallest — the heap is always inverted relative to the wording, because the root is the eviction candidate, not the answer.
⭐ "This is O(n log k) versus sorting's O(n log n). When does that actually matter?"
When
k ≪ n. Atn = 10^4andk = 10,log kis about 3 versuslog nabout 13 — roughly a 4× improvement, which is real but not dramatic at this size.Where it matters much more is space and streaming. The heap holds
kpoints, notn, so it works on a stream you can't store and on datasets that don't fit in memory. Sorting requires the whole array at once.
"Your comparator uses subtraction. You just warned against that."
Fair — and here the operands are squared distances up to
2 × 10^8, so a difference could be2 × 10^8, which is safe but uncomfortably close to needing thought. The cleaner form is:JavaComparator.comparingInt((int[] p) -> p[0]*p[0] + p[1]*p[1]).reversed()That's overflow-proof by construction and states "farthest first" directly. I'd write that version.
"You recompute the squared distance on every comparison. Is that wasteful?"
Slightly — each comparison does four multiplications. For
n = 10^4withlog kcomparisons each, it's negligible. If it mattered you'd store{dist, x, y}triples so the distance is computed once per point, trading a little memory for arithmetic.
"Does the output order matter?"
The problem says any order, which is why draining the heap is fine even though it yields farthest-to-closest. If sorted output were required I'd reverse the drained array, or use a min-heap over all
npoints and pollktimes.
Approach 3 — Quickselect
public int[][] kClosest(int[][] points, int k) {
int lo = 0, hi = points.length - 1;
while (lo < hi) {
int p = partition(points, lo, hi);
if (p == k - 1) break;
if (p < k - 1) lo = p + 1; else hi = p - 1;
}
return Arrays.copyOfRange(points, 0, k);
}
private int partition(int[][] pts, int lo, int hi) {
int pivot = dist(pts[hi]), store = lo;
for (int i = lo; i < hi; i++)
if (dist(pts[i]) < pivot) swap(pts, i, store++);
swap(pts, store, hi);
return store;
}
private int dist(int[] p) { return p[0]*p[0] + p[1]*p[1]; }
private void swap(int[][] a, int i, int j) { int[] t = a[i]; a[i] = a[j]; a[j] = t; }- Time
O(n)average,O(n²)worst · SpaceO(1)
Counter-questions on this approach
⭐ "Why is this O(n) when sorting is O(n log n)?"
Because it only recurses into the side that contains the answer. Sorting orders both halves; quickselect discards one entirely at each step.
The work is
n + n/2 + n/4 + … = 2non average, soO(n). The trade is that the array ends up partially ordered — the firstkare the closest, but in no particular order among themselves — which is exactly what the problem asks for and nothing more.
⭐ "What's the worst case, and does it matter?"
O(n²), when every pivot is the minimum or maximum — for instance, an already-sorted array with last-element pivots. An adversary who knows your pivot rule can force it.The standard defence is a random pivot, which makes the bad case astronomically unlikely, or median-of-medians, which guarantees
O(n)at a much worse constant. In an interview I'd note the risk and use a random pivot.
"Does it mutate the input?"
Heavily — it reorders the array in place. That's how it achieves
O(1)space, and it's a real cost to mention.
"Which would you actually submit?"
The heap or the sort, and I'd say why: quickselect is the fastest on average but is the most code, has a bad worst case, and destroys the input ordering. At
n = 10^4the sort runs instantly. I'd write the heap if asked about streaming ork ≪ n, and mention quickselect as theO(n)option.
Comparison
| Approach | Time | Space | Mutates input | Streams? |
|---|---|---|---|---|
| Sort by distance | O(n log n) | O(1) | yes | no |
| Max-heap of size k | O(n log k) | O(k) | no | yes |
| Quickselect | O(n) avg, O(n²) worst | O(1) | yes | no |
4. Why the Optimal Wins
There isn't a single winner here, and saying so is better than pretending otherwise.
Against sorting: both the heap and quickselect avoid computing the full order when only a prefix is wanted. Sorting to read k of n items is doing n log n work for a k-sized answer.
Heap versus quickselect: the heap is O(n log k) with O(k) space and works on a stream; quickselect is O(n) average with O(1) space but needs the whole array and reorders it. For one query on an in-memory array, quickselect. For a stream or k ≪ n, the heap.
The framing worth keeping:
Comparing distances never needs the square root —
x² + y²orders identically and stays in integers. And for theksmallest, the heap is a MAX-heap, because its root is the eviction candidate.
5. Java Prerequisites
Heap of arrays with a safe comparator
PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
Comparator.comparingInt((int[] p) -> p[0]*p[0] + p[1]*p[1]).reversed());The explicit (int[] p) type is needed — without it, type inference fails on the chained reversed().
reversed() reverses the whole chain, not just the last key. With a single key that's fine; with thenComparing it's a classic trap. See 04.
Overflow check before writing the comparator. |coord| <= 10^4 → squared sum <= 2 × 10^8 → int is safe. At 10^5 it would not be.
Arrays.copyOfRange(arr, 0, k) returns the first k elements as a new array.
Arrays.sort on objects is a stable merge sort, O(n log n); on primitives it's dual-pivot quicksort and not stable.
6. Interview Communication Guide
Clarifying questions: Does output order matter (no — it permits the heap drain)? Can points repeat (yes; duplicates are fine)? Is the input array mutable (sorting and quickselect both reorder it)? Is this one query or a stream (it decides heap vs quickselect)? What's the coordinate range (10^4 — it decides int vs long)?
The pitch
"First, I'd skip the square root. I only ever compare distances, never report them, and
√is monotonically increasing — sox² + y²gives the identical ordering. That keeps everything in integers, avoiding floating point entirely.Worth checking the bound: coordinates up to
10^4give a squared sum up to2 × 10^8, which fits inintwith room to spare. At10^5I'd needlong.Then there are three reasonable approaches, and which is best depends on the setting.
Sorting by distance and taking the first
kis two lines andO(n log n). Atn = 10^4that's instant, and it's what I'd ship absent other requirements.A max-heap of size
kisO(n log k)andO(k)space. Max-heap, not min — I'm keeping theksmallest distances, so the element to evict when a closer point arrives is the largest of those kept, and that's what I need at the root. It's the mirror of the streaming question: min-heap for the k largest, max-heap for the k smallest. The heap is always inverted, because the root is the eviction candidate rather than the answer.Quickselect is
O(n)on average — it partitions and recurses only into the side containing the answer, so it never orders the rest. The worst case isO(n²)on adversarial pivots, which a random pivot defends against.So: heap if the data streams or
kis much smaller thann; quickselect for one query on an in-memory array; sorting if clarity matters more than the constant."
Edge cases to volunteer:
| Input | Expected | Tests |
|---|---|---|
k = points.length | all points | Nothing is ever evicted |
k = 1 | the single closest | Where O(n log k) beats sorting most |
| Points at the origin | distance 0 | Smallest possible |
| Two points equidistant | either | Ties — any order is acceptable |
| All points identical | any k of them | Duplicates must be kept |
[-10^4, -10^4] | 2 × 10^8 | The overflow boundary |
Name the equidistant case and the coordinate extreme. The first confirms you read "any order"; the second is where an implementation using long unnecessarily — or int incorrectly, at larger bounds — shows up.
7. Follow-Up Questions — Modified Constraints
⭐ "The points arrive as a stream too large to store."
Quickselect and sorting are both out — they need the whole array. The max-heap is the only option: it holds
kpoints regardless of stream length, atO(log k)per point. This is precisely where the heap'sO(k)space stops being a minor detail.
⭐ "Find the k closest points to an arbitrary point (a, b), not the origin."
Replace
x² + y²with(x−a)² + (y−b)². Nothing structural changes — but re-check overflow: with coordinates and the target both at10^4, a difference can be2 × 10^4and its square4 × 10^8, so the sum reaches8 × 10^8. Stillint, but the margin has shrunk fourfold. That's the kind of thing worth recomputing rather than carrying over.
"Answer many k-closest queries against different targets."
Build a k-d tree or ball tree once,
O(n log n), then each query isO(log n + k)on average. That's the right structure when queries dominate — a heap would rescan allnpoints per query.
"Return them sorted by distance."
Drain the max-heap and reverse,
O(k log k). Or use a min-heap over allnand pollktimes,O(n + k log n)withO(n)heapify — which is better whenkis close ton.
"What if the points were in 3D, or d dimensions?"
The comparator becomes a sum over
dcoordinates. Everything else is unchanged, but overflow gets closer:dterms each up to10^8meanslongbecomes necessary aroundd = 20. In high dimensions the k-d tree also degrades toward linear scan — the curse of dimensionality — so approximate methods like LSH take over.
"Find the k closest pairs among the points, not to a fixed origin."
Different problem, and much harder. Naively
O(n²)pairs fed through a size-kheap. Closest-pair-of-points has a classicO(n log n)divide-and-conquer solution; the k-closest-pairs generalisation is harder still. Worth naming that it isn't a variation of this technique.