23. K Closest Points to Origin

βœ… GFG solution to the K Closest Points to Origin problem: find k nearest points to origin using efficient selection algorithms like nth_element, sorting, and heaps. πŸš€

The problem can be found at the following link: πŸ”— Question Link

🧩 Problem Description

Given an integer k and an array of points points[][], where each point is represented as points[i] = [xi, yi] on the X-Y plane, return the k closest points to the origin (0, 0).

The distance between two points on the X-Y plane is the Euclidean distance, defined as:

distance = sqrt((x2 - x1)Β² + (y2 - y1)Β²)

You can return the k closest points in any order. The driver code will print them in sorted order. Test cases are generated such that there will be a unique answer.

πŸ“˜ Examples

Example 1

Input: k = 2, points[] = [[1, 3], [-2, 2], [5, 8], [0, 1]]
Output: [[-2, 2], [0, 1]]
Explanation: The Euclidean distances from the origin are:
Point (1, 3) = sqrt(10)
Point (-2, 2) = sqrt(8)
Point (5, 8) = sqrt(89)
Point (0, 1) = sqrt(1)
The two closest points to the origin are [-2, 2] and [0, 1].

Example 2

Input: k = 1, points = [[2, 4], [-1, -1], [0, 0]]
Output: [[0, 0]]
Explanation: The Euclidean distances from the origin are:
Point (2, 4) = sqrt(20)
Point (-1, -1) = sqrt(2)
Point (0, 0) = sqrt(0)
The closest point to origin is (0, 0).

πŸ”’ Constraints

  • $1 \le k \le \text{points.size()} \le 10^5$

  • $-3 \times 10^4 \le x_i, y_i \le 3 \times 10^4$

βœ… My Approach

The optimal approach uses STL's nth_element algorithm for efficient partial sorting:

nth_element with Lambda Comparator

  1. Calculate Distance:

    • For each point, the squared Euclidean distance from origin is xΒ² + yΒ².

    • We avoid computing square roots since comparing squared distances preserves ordering.

  2. Partial Sorting with nth_element:

    • Use nth_element to partition the array such that the k smallest elements (by distance) are placed before position k.

    • The algorithm rearranges elements so that:

      • Elements before position k are ≀ the element at position k

      • Elements after position k are β‰₯ the element at position k

    • No full sorting is required, making it more efficient than complete sort.

  3. Custom Comparator:

    • Lambda function compares squared distances: a[0]*a[0]+a[1]*a[1] < b[0]*b[0]+b[1]*b[1]

    • This ensures points are ordered by their proximity to origin.

  4. Extract Result:

    • Return the first k elements from the rearranged array.

    • These k elements are guaranteed to be the k closest points.

πŸ“ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(n) on average, where n is the number of points. The nth_element algorithm uses introselect (a hybrid of quickselect and heapselect), which has linear average-case performance. In the worst case, it can be O(n log n), but this is rare with good pivot selection.

  • Expected Auxiliary Space Complexity: O(1), as nth_element performs in-place partitioning without requiring additional data structures. Only a constant amount of space is used for variables and the lambda function.

πŸ§‘β€πŸ’» Code (C++)

class Solution {
public:
    vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
        nth_element(points.begin(), points.begin() + k, points.end(),
            [](auto& a, auto& b) { return a[0]*a[0]+a[1]*a[1] < b[0]*b[0]+b[1]*b[1]; });
        return vector<vector<int>>(points.begin(), points.begin() + k);
    }
};
⚑ View Alternative Approaches with Code and Analysis

πŸ“Š 2️⃣ Min Heap Approach

πŸ’‘ Algorithm Steps:

  1. Push all points with their squared distances into a min heap.

  2. Extract the first k elements from the heap.

  3. These k elements are the closest points to the origin.

  4. Return the result vector containing these k points.

class Solution {
public:
    vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
        priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> minHeap;
        for(int i = 0; i < points.size(); i++)
            minHeap.push({points[i][0]*points[i][0]+points[i][1]*points[i][1], i});
        vector<vector<int>> res;
        while(k--) {
            res.push_back(points[minHeap.top().second]);
            minHeap.pop();
        }
        return res;
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n log n) - Building heap and extracting k elements

  • Auxiliary Space: πŸ’Ύ O(n) - Space for heap storage

βœ… Why This Approach?

  • Straightforward heap-based solution

  • Natural ordering of elements by distance

  • Good when k is close to n

πŸ“Š 3️⃣ Sorting Approach

πŸ’‘ Algorithm Steps:

  1. Calculate squared distance for each point.

  2. Sort all points based on their distance from origin.

  3. Take the first k points from the sorted array.

  4. Return these k points as the result.

class Solution {
public:
    vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
        sort(points.begin(), points.end(), [](auto& a, auto& b) {
            return a[0]*a[0]+a[1]*a[1] < b[0]*b[0]+b[1]*b[1];
        });
        return vector<vector<int>>(points.begin(), points.begin() + k);
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n log n) - Sorting operation dominates

  • Auxiliary Space: πŸ’Ύ O(1) - In-place sorting

βœ… Why This Approach?

  • Clean and simple implementation

  • Modifies input array in-place

  • Easy to understand logic

πŸ“Š 4️⃣ Quickselect Partition

πŸ’‘ Algorithm Steps:

  1. Use quickselect algorithm to partition array around kth element.

  2. Recursively partition until k closest elements are on the left side.

  3. No need to fully sort the array, just partition correctly.

  4. Return the first k elements after partitioning.

class Solution {
public:
    vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
        function<void(int,int)> quickselect = [&](int l, int r) {
            if(l >= r) return;
            int p = l, i = l;
            for(int j = l; j < r; j++)
                if(points[j][0]*points[j][0]+points[j][1]*points[j][1] < points[r][0]*points[r][0]+points[r][1]*points[r][1])
                    swap(points[i++], points[j]);
            swap(points[i], points[r]);
            if(i < k) quickselect(i+1, r);
            else if(i > k) quickselect(l, i-1);
        };
        quickselect(0, points.size()-1);
        return vector<vector<int>>(points.begin(), points.begin()+k);
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n) average, O(nΒ²) worst - Quickselect partitioning

  • Auxiliary Space: πŸ’Ύ O(log n) - Recursion stack space

βœ… Why This Approach?

  • Better average time complexity than sorting

  • Efficient for large datasets with small k

  • Classic selection algorithm pattern

πŸ“Š 5️⃣ Max Heap (Original Approach)

πŸ’‘ Algorithm Steps:

  1. Maintain a max heap of size k to store the k closest points.

  2. For each point, calculate its squared distance.

  3. If heap size is less than k, insert the point directly.

  4. Otherwise, compare with the farthest point in heap (top element) and replace if current point is closer.

class Solution {
public:
    vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
        priority_queue<pair<int, vector<int>>> maxHeap;
        for(auto& p : points) {
            int dist = p[0]*p[0] + p[1]*p[1];
            if(maxHeap.size() < k) maxHeap.push({dist, p});
            else if(dist < maxHeap.top().first) {
                maxHeap.pop();
                maxHeap.push({dist, p});
            }
        }
        vector<vector<int>> res;
        while(!maxHeap.empty()) {
            res.push_back(maxHeap.top().second);
            maxHeap.pop();
        }
        return res;
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n log k) - Each insertion/deletion in heap of size k takes log k time

  • Auxiliary Space: πŸ’Ύ O(k) - Heap stores at most k elements

βœ… Why This Approach?

  • Space-efficient when k is small compared to n

  • Optimal for streaming data or when n is very large

  • Maintains only k elements at any time

πŸ†š πŸ” Comparison of Approaches

πŸš€ Approach

⏱️ Time Complexity

πŸ’Ύ Space Complexity

βœ… Pros

⚠️ Cons

🎯 nth_element

🟒 O(n) avg

🟒 O(1)

πŸš€ Optimal average performance

πŸ”§ Modifies input array

πŸ”Ί Min Heap

🟑 O(n log n)

🟑 O(n)

πŸ“– Clear heap semantics

πŸ’Ύ Extra space for heap

πŸ“Š Sorting

🟑 O(n log n)

🟒 O(1)

🎯 Simple and clean

🐌 Overkill for partial sorting

⚑ Quickselect

🟒 O(n) avg

🟒 O(log n)

⭐ Good average performance

πŸ”§ Complex implementation

πŸ”οΈ Max Heap

🟒 O(n log k)

🟒 O(k)

πŸ’Ύ Space-efficient for small k

🐌 Slower when k β‰ˆ n

πŸ† Best Choice Recommendation

🎯 Scenario

πŸŽ–οΈ Recommended Approach

πŸ”₯ Performance Rating

πŸ… Optimal performance needed

πŸ₯‡ nth_element

β˜…β˜…β˜…β˜…β˜…

πŸ“– Need all sorted

πŸ₯ˆ Sorting

β˜…β˜…β˜…β˜…β˜†

πŸ”§ Cannot modify input

πŸ₯‰ Min Heap

β˜…β˜…β˜…β˜†β˜†

🎯 Large n, small k

πŸ… Max Heap

β˜…β˜…β˜…β˜…β˜…

πŸ’‘ Interview/Competitive

πŸŽ–οΈ Quickselect

β˜…β˜…β˜…β˜…β˜†

β˜• Code (Java)

class Solution {
    public ArrayList<ArrayList<Integer>> kClosest(int[][] points, int k) {
        Arrays.sort(points, (a, b) -> (a[0]*a[0] + a[1]*a[1]) - (b[0]*b[0] + b[1]*b[1]));
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        for (int i = 0; i < k; i++)
            res.add(new ArrayList<>(Arrays.asList(points[i][0], points[i][1])));
        return res;
    }
}

🐍 Code (Python)

class Solution:
    def kClosest(self, points, k):
        points.sort(key=lambda x: x[0]**2 + x[1]**2)
        return points[:k]

🧠 Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: πŸ“¬ Any Questions?. Let's make this learning journey more collaborative!

⭐ If you find this helpful, please give this repository a star! ⭐


πŸ“Visitor Count

Visitor counter

Last updated