题目描述
思路解析动画文字版
核心一句话:大小 k 的最大堆,堆顶是当前 k 个里最远的;新点比堆顶近就替换,最后堆里就是最近的 k 个,不用全排序。
先建空堆:把每个点按距离 d² 加进大小为 3 的最大堆。堆是完全二叉树,画成树看更直观(最远的在堆顶)。
处理 points[0] = (1,3)。堆里还不够 k=3 个,新点 (1,3)(d²=10)直接放到堆末尾,再按距离上浮到正确位置(远的在上)。
处理完 (1,3)。堆里还没满 3 个,暂时先把见到的点都留着。
处理 points[1] = (-2,2)。堆里还不够 k=3 个,新点 (-2,2)(d²=8)直接放到堆末尾,再按距离上浮到正确位置(远的在上)。
(-2,2) 的距离 d²=8 不大于父节点 (1,3) 的 10,已满足「父 ≥ 子」,上浮结束,位置定下来。
处理完 (-2,2)。堆里还没满 3 个,暂时先把见到的点都留着。
处理 points[2] = (5,8)。堆里还不够 k=3 个,新点 (5,8)(d²=89)直接放到堆末尾,再按距离上浮到正确位置(远的在上)。
新点 (5,8) 的距离 d²=89 比父节点 (1,3) 的 10 还大(最大堆要远的在上面),交换它俩,让它继续往上浮。
交换完成:更远的 (5,8)(d²=89)升到父位置。继续拿它和再上面的父节点比,直到不再更远。
处理完 (5,8)。堆里就是目前最近的 3 个点;堆顶 (5,8)(绿色)是这 3 个里最远的,下一个新点只要比它近就能替换它。
处理 points[3] = (0,1)。堆里已是目前最近的 3 个,堆顶 (5,8)(d²=89)是其中最远的。新点 (0,1) 的 d²=1 比堆顶近,说明它该进前 3 近,挤掉最远的堆顶。
把堆顶替换成新点 (0,1)(紫色,d²=1)。此刻它可能比孩子近,违反最大堆,需要下沉到正确位置。
堆顶 (0,1)(d²=1)比孩子 (1,3)(d²=10)近,违反最大堆,得把更远的孩子换上来,让近的继续往下沉。
交换完成:更远的 (1,3)(d²=10)回到上面。继续拿沉下去的 (0,1) 和它的孩子比,直到就位。
节点 (0,1)(d²=1)已不小于它的孩子,满足最大堆「父 ≥ 子」,下沉结束,堆顶重新成为当前最远点。
处理完 (0,1)。堆里就是目前最近的 3 个点;堆顶 (1,3)(绿色)是这 3 个里最远的,下一个新点只要比它近就能替换它。
处理 points[4] = (3,4)。新点 (3,4) 的 d²=25 不比堆顶 (1,3)(d²=10)近,进不了「最近的 3 个」,堆不变,直接丢弃。
处理完 (3,4)。堆里就是目前最近的 3 个点;堆顶 (1,3)(绿色)是这 3 个里最远的,下一个新点只要比它近就能替换它。
处理 points[5] = (-1,-1)。堆里已是目前最近的 3 个,堆顶 (1,3)(d²=10)是其中最远的。新点 (-1,-1) 的 d²=2 比堆顶近,说明它该进前 3 近,挤掉最远的堆顶。
把堆顶替换成新点 (-1,-1)(紫色,d²=2)。此刻它可能比孩子近,违反最大堆,需要下沉到正确位置。
堆顶 (-1,-1)(d²=2)比孩子 (-2,2)(d²=8)近,违反最大堆,得把更远的孩子换上来,让近的继续往下沉。
交换完成:更远的 (-2,2)(d²=8)回到上面。继续拿沉下去的 (-1,-1) 和它的孩子比,直到就位。
节点 (-1,-1)(d²=2)已不小于它的孩子,满足最大堆「父 ≥ 子」,下沉结束,堆顶重新成为当前最远点。
处理完 (-1,-1)。堆里就是目前最近的 3 个点;堆顶 (-2,2)(绿色)是这 3 个里最远的,下一个新点只要比它近就能替换它。
处理 points[6] = (2,-3)。新点 (2,-3) 的 d²=13 不比堆顶 (-2,2)(d²=8)近,进不了「最近的 3 个」,堆不变,直接丢弃。
处理完 (2,-3)。堆里就是目前最近的 3 个点;堆顶 (-2,2)(绿色)是这 3 个里最远的,下一个新点只要比它近就能替换它。
走完所有点,堆里留下的就是离原点最近的 3 个点:(0,1)、(-1,-1)、(-2,2)。堆顶 (-2,2)(绿色)是这 3 个里最远的。全程只维护了一个大小 3 的堆,没对全部点排序。
边界都围绕「堆是否满」和「新点 d² 与堆顶的大小」。
两个高频追问:免开根号、与 Quickselect/排序的取舍。
参考代码
import heapqclass Solution: def kClosest(self, points, k): h = [] # 最大堆:存 (-d², point) for x, y in points: d = x * x + y * y if len(h) < k: heapq.heappush(h, (-d, x, y)) elif -d > h[0][0]: # d < 堆顶距离 → 更近 heapq.heapreplace(h, (-d, x, y)) return [[x, y] for _, x, y in h]复杂度
- 时间:O(n log k),每个点一次入堆/换堆顶 = O(log k),共 n 个点
- 空间:O(k),堆里始终只存 k 个点
易错点
面试追问把动画讲成自己的话
追问为什么比较距离时不开根号?
追问除了堆,还有别的做法吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
数组中第 K 个最大元素
LeetCode 215 · 中等 · 沿着 堆 / 优先队列 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题