[路飞]_leetcode刷题_973. 最接近原点的 K 个点

213 阅读2分钟

题目

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)