在问 · 算法题解

973. 最接近原点的 K 个点

题目描述

输入数组 points,points[i] = [xi, yi] 是平面上的一个点;另给一个整数 k。

点到原点 (0,0) 的距离按欧几里得距离计算,即 sqrt(x^2 + y^2)。

返回距离原点最近的 k 个点,点的顺序任意。题目保证这 k 个点组成的集合是唯一的。

示例 1
输入:points = [[1,3],[-2,2]], k = 1
输出:[[-2,2]]

解释:(-2,2) 的距离是 sqrt(8),小于 (1,3) 的 sqrt(10)。

示例 2
输入:points = [[3,3],[5,-1],[-2,4]], k = 2
输出:[[-2,4],[3,3]]

解释:前两近的点是 (3,3) 和 (-2,4),按任意顺序返回都可以。

数据范围

  • 1 <= k <= points.length <= 10^4
  • -10^4 < xi, yi < 10^4

解题思路:大顶堆

维护一个大小不超过 k 的大顶堆,堆顶是当前候选中最远的点。逐个把点放入堆,一旦超过 k 个,就把最远的那个弹出。这样堆里始终是已扫描点中最近的 k 个。比较距离时用平方 x*x+y*y,避免开根号。Python 的 heapq 是小顶堆,所以存 -d 来模拟大顶堆。

为什么正确

处理完前 i 个点后,堆中恰好是这 i 个点里距离最小的 min(i, k) 个,堆顶是其中最远的。

复杂度分析

时间 O(n log k),空间 O(k)。

易错点

用小顶堆存距离(不取负)会弹出最近的点,结果恰好相反;另外比较的是距离平方,不需要也不应该开根号引入浮点误差。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        heap = []
        for x, y in points:
            d = x * x + y * y
            heapq.heappush(heap, (-d, x, y))
            if len(heap) > k:
                heapq.heappop(heap)
        return [[x, y] for _, x, y in heap]

载入编辑器,自己跑一遍 →

其他解法

排序最简单;大顶堆 O(n log k) 适合 k 远小于 n 或数据流式到达的情况;快速选择平均 O(n) 最快,但最坏情况较差。

解法 2:排序 时间 O(n log n),空间 O(log n)

按到原点距离的平方 x² + y² 从小到大排序,取前 k 个点。比较时使用平方而不开根号,避免浮点误差。

代码最短,思路最直接。需要对全部 n 个点排序,k 很小时比堆慢。

class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        points.sort(key=lambda p: p[0] * p[0] + p[1] * p[1])
        return points[:k]

解法 3:快速选择 时间 平均 O(n),最坏 O(n²),空间 O(1)

用快速排序的划分思想,按距离把数组三路划分为小于、等于、大于基准的三段。若第 k 个位置落在小于段,就只在左段继续;落在大于段就只在右段继续;落在等于段则前 k 个已经是答案。最后返回前 k 个点。

平均线性时间且原地完成,是理论上最快的方法。最坏情况退化到 O(n²),而且会打乱输入数组。

class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        def dist(p):
            return p[0] * p[0] + p[1] * p[1]

        lo, hi = 0, len(points) - 1
        while lo < hi:
            pivot = dist(points[(lo + hi) // 2])
            lt, i, gt = lo, lo, hi
            while i <= gt:
                d = dist(points[i])
                if d < pivot:
                    points[lt], points[i] = points[i], points[lt]
                    lt += 1
                    i += 1
                elif d > pivot:
                    points[gt], points[i] = points[i], points[gt]
                    gt -= 1
                else:
                    i += 1
            if k - 1 < lt:
                hi = lt - 1
            elif k - 1 > gt:
                lo = gt + 1
            else:
                break
        return points[:k]

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →