🧠
Quickselect Algorithm (Average O(n))
💡 Quickselect is an advanced approach that finds the k closest points without fully sorting, using partitioning similar to quicksort, which is optimal on average.
Intuition
Use a partitioning method to place points closer than a pivot to the left and farther to the right, then recurse only on the side containing the kth closest point until the k closest points are found.
Algorithm
- Choose a pivot point and compute its distance.
- Partition points so those closer than pivot come before it.
- Check the pivot’s final position:
- - If pivot index == k, first k points are closest.
- - Else recurse on the side containing the kth point.
- Return first k points after partitioning.
💡 Quickselect reduces work by focusing only on the part of the array that contains the kth closest point.
from typing import List
import random
def kClosest(points: List[List[int]], k: int) -> List[List[int]]:
def dist(i):
return points[i][0]*points[i][0] + points[i][1]*points[i][1]
def partition(left, right, pivot_index):
pivot_dist = dist(pivot_index)
points[pivot_index], points[right] = points[right], points[pivot_index]
store_index = left
for i in range(left, right):
if dist(i) < pivot_dist:
points[store_index], points[i] = points[i], points[store_index]
store_index += 1
points[right], points[store_index] = points[store_index], points[right]
return store_index
def quickselect(left, right, k_smallest):
if left == right:
return
pivot_index = random.randint(left, right)
pivot_index = partition(left, right, pivot_index)
if k_smallest == pivot_index:
return
elif k_smallest < pivot_index:
quickselect(left, pivot_index - 1, k_smallest)
else:
quickselect(pivot_index + 1, right, k_smallest)
quickselect(0, len(points) - 1, k - 1)
return points[:k]
# Driver code
if __name__ == '__main__':
pts = [[3,3],[5,-1],[-2,4]]
k = 2
print(kClosest(pts, k)) # Expected output: [[3,3],[-2,4]] or similar
Line Notes
pivot_index = random.randint(left, right)Random pivot to avoid worst-case performance
points[pivot_index], points[right] = points[right], points[pivot_index]Move pivot to end for partitioning
if dist(i) < pivot_dist:Compare distances to pivot to partition points
quickselect(0, len(points) - 1, k - 1)Start quickselect to find kth closest point
import java.util.*;
public class KClosestPoints {
private static int dist(int[] point) {
return point[0]*point[0] + point[1]*point[1];
}
private static int partition(int[][] points, int left, int right, int pivotIndex) {
int pivotDist = dist(points[pivotIndex]);
swap(points, pivotIndex, right);
int storeIndex = left;
for (int i = left; i < right; i++) {
if (dist(points[i]) < pivotDist) {
swap(points, storeIndex, i);
storeIndex++;
}
}
swap(points, storeIndex, right);
return storeIndex;
}
private static void swap(int[][] points, int i, int j) {
int[] temp = points[i];
points[i] = points[j];
points[j] = temp;
}
private static void quickselect(int[][] points, int left, int right, int k) {
if (left >= right) return;
Random rand = new Random();
int pivotIndex = left + rand.nextInt(right - left + 1);
pivotIndex = partition(points, left, right, pivotIndex);
if (k == pivotIndex) return;
else if (k < pivotIndex) quickselect(points, left, pivotIndex - 1, k);
else quickselect(points, pivotIndex + 1, right, k);
}
public static int[][] kClosest(int[][] points, int k) {
quickselect(points, 0, points.length - 1, k - 1);
return Arrays.copyOfRange(points, 0, k);
}
public static void main(String[] args) {
int[][] points = {{3,3},{5,-1},{-2,4}};
int k = 2;
int[][] res = kClosest(points, k);
System.out.println(Arrays.deepToString(res)); // Expected: two closest points
}
}
Line Notes
import java.util.Random;Import Random class for pivot selection
int pivotIndex = left + rand.nextInt(right - left + 1)Random pivot selection to avoid worst-case
swap(points, pivotIndex, right)Move pivot to end for partitioning
if (dist(points[i]) < pivotDist)Partition points by comparing distances
quickselect(points, 0, points.length - 1, k - 1)Start quickselect to find kth closest
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
int dist(const vector<int>& point) {
return point[0]*point[0] + point[1]*point[1];
}
int partition(vector<vector<int>>& points, int left, int right, int pivotIndex) {
int pivotDist = dist(points[pivotIndex]);
swap(points[pivotIndex], points[right]);
int storeIndex = left;
for (int i = left; i < right; i++) {
if (dist(points[i]) < pivotDist) {
swap(points[storeIndex], points[i]);
storeIndex++;
}
}
swap(points[storeIndex], points[right]);
return storeIndex;
}
void quickselect(vector<vector<int>>& points, int left, int right, int k) {
if (left >= right) return;
int pivotIndex = left + rand() % (right - left + 1);
pivotIndex = partition(points, left, right, pivotIndex);
if (k == pivotIndex) return;
else if (k < pivotIndex) quickselect(points, left, pivotIndex - 1, k);
else quickselect(points, pivotIndex + 1, right, k);
}
int main() {
srand(time(0));
vector<vector<int>> points = {{3,3},{5,-1},{-2,4}};
int k = 2;
quickselect(points, 0, points.size() - 1, k - 1);
for (int i = 0; i < k; i++) {
cout << '[' << points[i][0] << ',' << points[i][1] << ']';
}
cout << endl; // Expected output: two closest points
return 0;
}
Line Notes
int pivotIndex = left + rand() % (right - left + 1)Random pivot to avoid worst-case
swap(points[pivotIndex], points[right])Move pivot to end for partitioning
if (dist(points[i]) < pivotDist)Partition points by distance comparison
quickselect(points, 0, points.size() - 1, k - 1)Start quickselect to find kth closest
function kClosest(points, k) {
function dist(i) {
return points[i][0]*points[i][0] + points[i][1]*points[i][1];
}
function swap(i, j) {
[points[i], points[j]] = [points[j], points[i]];
}
function partition(left, right, pivotIndex) {
let pivotDist = dist(pivotIndex);
swap(pivotIndex, right);
let storeIndex = left;
for (let i = left; i < right; i++) {
if (dist(i) < pivotDist) {
swap(storeIndex, i);
storeIndex++;
}
}
swap(storeIndex, right);
return storeIndex;
}
function quickselect(left, right, kSmallest) {
if (left === right) return;
let pivotIndex = left + Math.floor(Math.random() * (right - left + 1));
pivotIndex = partition(left, right, pivotIndex);
if (kSmallest === pivotIndex) return;
else if (kSmallest < pivotIndex) quickselect(left, pivotIndex - 1, kSmallest);
else quickselect(pivotIndex + 1, right, kSmallest);
}
quickselect(0, points.length - 1, k - 1);
return points.slice(0, k);
}
// Test
const points = [[3,3],[5,-1],[-2,4]];
const k = 2;
console.log(kClosest(points, k)); // Expected output: two closest points
Line Notes
let pivotIndex = left + Math.floor(Math.random() * (right - left + 1))Random pivot selection to avoid worst-case
swap(pivotIndex, right)Move pivot to end for partitioning
if (dist(i) < pivotDist)Partition points by comparing distances
quickselect(0, points.length - 1, k - 1)Start quickselect to find kth closest
TimeAverage O(n), worst O(n^2)
SpaceO(1) additional space
Quickselect partitions the array and recurses only on one side, reducing average time to linear; worst case is quadratic but rare with random pivots.
💡 For n=100,000, this approach can find k closest points in about 100,000 operations on average, much faster than sorting.
Interview Verdict: Accepted and optimal average time
This is the best approach to mention and implement if comfortable, showing mastery of selection algorithms.