题目
973. 最接近原点的 K 个点
我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。
(这里,平面上两点之间的距离是欧几里德距离。)
你可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。
示例 1:
输入:points = [[1,3],[-2,2]], K = 1
输出:[[-2,2]]
解释:
(1, 3) 和原点之间的距离为 sqrt(10),
(-2, 2) 和原点之间的距离为 sqrt(8),
由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更近。
我们只需要距离原点最近的 K = 1 个点,所以答案就是 [[-2,2]]。
示例 2:
输入:points = [[3,3],[5,-1],[-2,4]], K = 2
输出:[[3,3],[-2,4]]
(答案 [[-2,4],[3,3]] 也会被接受。)
思路1:
暴力解法。
- 哈希表处理节点和距离的映射关系
- 将哈希表转为数组,并按距离排序
- 取出前k个数即可
代码如下:
/**
* @param {number[][]} points
* @param {number} k
* @return {number[][]}
*/
var kClosest = function(points, k) {
let map = new Map();
for(let i=0;i<points.length;i++){
lenArr = Math.pow(Math.abs(points[i][0]),2)+Math.pow(Math.abs(points[i][1]),2);
map.set(points[i],lenArr)
}
let res = Array.from(map);
res = res.sort((a,b)=>a[1]-b[1]);
let result = [];
for(let i=0;i<k;i++){
result.push(res[i][0])
}
return result;
};
复杂度分析
时间复杂度:O(nlogn),处理哈希表为O(n),主要是这里用的js自带的sort排序,chrome V8引擎当数组长度小于10时使用的是插入排序,大于10使用的是快速排序,所以为O(nlogn);
空间复杂度:O(n),哈希表的长度为n
思路2:
大顶堆。
- 还是使用哈希表先处理一个节点和距离的映射表
- 使用一个长度为k堆大顶堆去维护节点
- 开始k个元素直接入堆
- 当第k+1个元素开始,和堆顶元素比较,谁更小,就留下谁
最后返回堆内的k个节点,即为要找的最小的k个节点
代码实现:
var kClosest = function(points, k) {
let map = new Map();
for(let i=0;i<points.length;i++){
let len = Math.pow(points[i][0],2) + Math.pow(points[i][1],2)
map.set(points[i],len);
}
let arr = Array.from(map);
let cmp = (a,b)=>{
return b[1] > a[1]
}
let maxHeap = new Heap(cmp);
for(let i=0;i<arr.length;i++){
if(maxHeap.size()<k){
maxHeap.offer(arr[i])
}else if(arr[i][1] < maxHeap.peek()[1]){
maxHeap.poll();
maxHeap.offer(arr[i])
}
}
let res = [];
for(let i=0;i<maxHeap.size();i++){
res.push(maxHeap.heap[i][0]);
}
return res;
};
let defaultCmp =(a,b)=>{
return a[1] > b[1]
}
class Heap{
constructor(cmp=defaultCmp){
this.cmp = cmp;
this.heap = [];
}
offer(node){
this.heap.push(node);
this.siftUp(this.heap.length-1);
}
poll(){
let node = this.heap[0];
this.heap[0] = this.heap[this.heap.length-1];
this.heap.pop();
this.siftDown(0);
return node;
}
size(){
return this.heap.length;
}
peek(){
return this.heap[0];
}
siftUp(i){
// 上浮,直到到达顶点
while(i>0){
// 顶点下标是0开始的话,子节点为i,父节点为Math.floor((i-1)/2)
let parent = Math.floor((i-1)/2);
// 和父节点比较大小,如果父节点大就交换
if(this.cmp(this.heap[parent],this.heap[i])){
[this.heap[parent],this.heap[i]] = [this.heap[i],this.heap[parent]]
i = parent;
}else{
// 如果父节点更小的话,可以直接弹出循环了,再往上节点值只会更小
break;
}
}
}
siftDown(i){
// 顶点下表从0开始,那么左子节点下标为i*2+1
// 每次都判断当前的i是否有左子节点
while(i*2+1<this.heap.length){
// 根据i算出左子节点
let child = i*2+1;
// 如果右子节点存在,且小于左子节点的时候,我们取右子节点来跟当前节点比较
if(child+1<this.heap.length && this.cmp(this.heap[child],this.heap[child+1])){
child++;
}
// 比较当前节点和子节点的大小
if(this.cmp(this.heap[i],this.heap[child])){
[this.heap[i],this.heap[child]] = [this.heap[child],this.heap[i]]
i = child;
}else{
break;
}
}
}
}
复杂度分析
时间复杂度:O(nlogk),大顶堆单次的维护操作复杂度为O(logk),最坏的情况入堆数量为n,则时间复杂度为O(nlogk)
空间复杂度:O(k),map对象和数据都需要将所有节点都存进去为O(n),大顶堆的占用O(k),所以取O(n)