[路飞]_leetcode刷题_面试题 17.14. 最小K个数

147 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

题目

面试题 17.14. 最小K个数

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]

解法一:

思路:

暴力解法。我们用各种排序,比如选择排序,冒泡排序,插入排序等。

我们这里就来个最暴力的,选择排序一波。

我们只需要选择k次即可,然后返回数组的前k项即可

代码如下

/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
var smallestK = function(arr, k) {
    for(let i=0;i<k;i++){
        let minIndex = i;
        for(let j=i+1;j<arr.length;j++){
            if(arr[minIndex]>arr[j]){
                minIndex = j;
            }
        }
        if(minIndex != i){
            [arr[minIndex], arr[i]] = [arr[i], arr[minIndex]]
        }
       
    }
    return arr.slice(0,k)
};

复杂度分析

时间复杂度:O(n*k)

空间复杂度:O(1)

解法二

思路

堆排序。

  1. 我们可以建立一个大顶堆,依次往大顶堆里推入数字。
  2. 当大顶堆长度小于k时,直接推入。
  3. 当大顶堆长度不小于k时,判断当前元素和堆顶元素的大小,
    • 如果当前元素小,则先将大顶堆堆顶元素弹出,然后将当前元素推入。
    • 如果当前元素大,则跳过。
  4. 返回大顶堆的heap数组即可。由于前面做了处理,heap数组刚好是k项。

代码如下

/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
var smallestK = function(arr, k) {
    let maxHeap = new Heap();
    for(let i=0; i<arr.length; i++){
        if(maxHeap.size() < k){
            maxHeap.offer(arr[i])
        }else if (arr[i] < maxHeap.peek()){
            maxHeap.poll();
            maxHeap.offer(arr[i]);
        }
    }
    return maxHeap.heap;

};
let defaultCmp = (a,b) => a>b;
class Heap{
    constructor(cmp = defaultCmp){
        this.cmp = cmp;
        this.heap = [];
    }
    size(){
        return this.heap.length;
    }
    peek(){
        return this.heap[0];
    }
    offer(node){
        this.heap.push(node);
        this.siftUp(this.heap.length-1);
    }
    poll(){
        this.heap[0] = this.heap.pop();
        this.siftDown(0)
    }
    siftUp(i){
        let parent = Math.floor((i-1)/2);
        if(parent>=0 && this.cmp(this.heap[i],this.heap[parent])){
            [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
            i = parent;
            this.siftUp(i);
        }
    }
    siftDown(i){
        let child = 2*i + 1;
        if(child<this.heap.length){
            // i < left
            if(this.cmp(this.heap[child],this.heap[i])){
                // i< left < right
                if(child+1<this.heap.length && this.cmp(this.heap[child+1],this.heap[child])){
                    [this.heap[i],this.heap[child+1]] = [this.heap[child+1], this.heap[i]];
                    i = child+1;
                // left > right,left>i
                }else{
                    [this.heap[i],this.heap[child]] = [this.heap[child], this.heap[i]];
                    i = child;
                }
                this.siftDown(i)
            }else if(child+1<this.heap.length && this.cmp(this.heap[child+1],this.heap[i])){
                [this.heap[i],this.heap[child+1]] = [this.heap[child+1], this.heap[i]];
                i = child+1;
                this.siftDown(i)
            }
        }
    }
}

复杂度分析

时间复杂度:O(klogk),当前入堆为logk,最快要入堆k次。

空间复杂度:O(k),堆存放的元素个数

解法三

思路:

快速排序。

直接快速排序,然后返回前k个的数组,这个单独拿来写,是因为和解法一那种O(n2)的排序相比,快速排序效率会高很多。

代码如下

/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
var smallestK = function(arr, k) {
    let result = arr;
    quickSort(result,0,result.length-1);
    return result.slice(0,k);
};

function quickSort(arr,left,right){
    if(left<right){
        let pIndex = partition(arr,left,right);
        quickSort(arr,left,pIndex-1);
        quickSort(arr,pIndex+1,right);
    }
}

function partition(arr,left,right){
    let randomIndex = Math.floor(Math.random()*(right-left+1))+left;
    [arr[left],arr[randomIndex]] = [arr[randomIndex], arr[left]];
    let pivot = arr[left];
    let lt = left;
    // all in [left,lt] < pivot
    // all in (lt+1,right] > pivot
    for(let i=left+1;i<=right;i++){
        if(arr[i]<pivot){
            lt++;
            [arr[i],arr[lt]] = [arr[lt],arr[i]];
        }
    }
    [arr[left],arr[lt]] = [arr[lt],arr[left]];
    return lt;
}

复杂度分析

时间复杂度:O(nlogn)

空间复杂度:O(1)

解法四

思路

快速排序优化版。

利用快速排序的特性,每次交换完,判断当前的pIndex在数组中的位置。

  • 如果pIndex == k,则直接返回
  • 如果pIndex < k ,那说明第k个元素在右边,则只用去右边找第k-pIndex个元素即可
  • 如果pIndex > k ,那说明第k个元素在左边,则只用去左边找第k个元素即可

代码后序再补上。