[路飞]_每天刷leetcode_27(最小K个数 Smallest K)

179 阅读1分钟

最小K个数

LeetCode传送门面试题 17.14. Smallest K LCCI

题目

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

Design an algorithm to find the smallest K numbers in an array.

Example:

Input: arr = [1,3,5,7,2,4,6,8], k = 4
Output: [1,2,3,4]

提示:

0 <= len(arr) <= 100000 0 <= k <= min(100000, len(arr))


思考线


解题思路

解题方式一

这道题和我们前面的数据流中的第K大元素类似,第一想法,我们可以用大顶堆来解决这个问题,只需要维护一个长度为k的大顶堆即可。

具体写堆的思路不再赘述,不太明白的同学可以通过我之前做过的学习内容来补充这方面的知识。下面是链接。

掘金文章:堆排序

B站视频:堆排序

B站视频:数据流中的第K大元素

我直接贴上我完成代码

function getLeastNumbers(arr: number[], k: number): number[] {
    const heap = new MaxHeap();
    for(let i = 0; i < arr.length; i ++) {
        if(heap.size() < k) {
            heap.offer(arr[i]);
        } else if( heap.peek() > arr[i]) {
            heap.poll();
            heap.offer(arr[i])
        }
    }
    let res = []
    for(let i = 0; i < k; i ++) {
        res.push(heap.poll())
    }
    return res;
};
class MaxHeap {
    data: number[];
    comparator: (a: number, b: number) => number
    constructor(nums: number[] = []) {
        this.data = nums;
        this.comparator = (a, b) => a - b;
        this.heapify()
    }
    heapify() {
        const len = this.size();
        for (let i = 1; i < len; i++) {
            this.bubbleUp(i);
        }
    }
    bubbleUp(index) {
        while (index>0) {
            let parent = (index - 1) >> 1;
            if (this.comparator(this.data[parent], this.data[index]) < 0) {
                this.swap(parent, index);
                index = parent;
            } else {
                break;
            }
        }
    }
    bubbleDown(index) {
        const len = this.size()
        while (index < len) {
            const left = index * 2 + 1;
            const right = index * 2 + 2;
            let findIndex = index;
            if(left <= len && this.comparator(this.data[findIndex], this.data[left])< 0) {
                findIndex = left;
            }
            if(right <= len && this.comparator(this.data[findIndex], this.data[right])< 0) {
                findIndex = right;
            }
            if(index !== findIndex) {
                this.swap(index, findIndex);
                index = findIndex;
            } else {
                break;
            }
        }


    }
    offer(val) {
        this.data.push(val);
        this.bubbleUp(this.size() - 1);
    }
    poll() {
        // 删除堆顶
        const res = this.data.shift();
        this.data.unshift(this.data.pop());
        this.bubbleDown(0)
        return res;
    }
    peek() {
        return this.size() ? this.data[0] : null;
    }
    size() {
        return this.data.length;
    }
    swap(index1, index2) {
        [this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
    }
}

解法二

这道题的本质是进行排序,如果看过我之前文章的同学可以知道,我做的前几期全部关于排序的算法,在这里都能完成本道题的解答。在这里我附上排序学习的链接,同时给出快排的解法。

选择、插入、冒泡、希尔、归并、快速排序

快速排序解题代码

function getLeastNumbers(arr: number[], k: number): number[] {
    fastSort(arr, 0, arr.length-1);
    const res = []
    while(k--) {
        res.push(arr.shift())
    }
    return res;
};

function fastSort(nums: number[], l: number, r: number): number[] {
    if(l >=r) return nums;
    const partitionIndex = partition(nums, l, r);
    fastSort(nums, l, partitionIndex - 1);
    fastSort(nums, partitionIndex + 1, r);
    return nums;
}
function partition(nums: number[], l, r): number {
    const pivot = l;
    let index = pivot + 1;
    while (l <= r) {
        l++
        if (nums[l] < nums[pivot]) {

            const temp = nums[l];
            nums[l] = nums[index];
            nums[index] = temp
            index++
        }
    }
    const temp = nums[pivot];
    nums[pivot] = nums[index-1];
    nums[index-1] = temp;

    return index -1;
}

解法三

在使用快速排序的思想来解答

有了上面快速排序的解答方案,我们能不能对这道题有针对性的优化我们的代码呢? 我们可以这样思考,每次执行完一次partition函数, 我们都能保证 pivot左边的值都小于它,pivot就是数组中的第partitionIndex - L + 1小的数。

  1. 如果partitionIndex - L + 1 === k,则 该位置就是数组中第k小的数,直接返回。
  2. 如果partitionIndex - L + 1 > k,则第k小的数在左侧,我们只要递归调用fastSort(nums, l, partitionIndex + 1, k)即可。
  3. 如果partitionIndex - L + 1 < k,则第k小的数在右侧,我们只要递归调用fastSort(nums, partitionIndex + 1, r, k - (partitionIndex - L + 1))即可。

最后我们只要返回arr的前k个元素即可。

代码如下

function getLeastNumbers(arr: number[], k: number): number[] {
    findLastK(arr, 0, arr.length - 1, k)
    return arr.slice(0, k);
};
function findLastK(nums, l, r, k) {
    const partationIndex = partition(nums, l, r);
    if (l >= r) return nums;
    if (partationIndex -l +1 === k) return nums;
    if (partationIndex-l +1 > k) {
        findLastK(nums, l, partationIndex - 1, k);
    } else {
        findLastK(nums, partationIndex + 1, r, k - (partationIndex -l +1))
    }
    return nums;
}
function partition(nums: number[], l, r): number {
    const pivot = l;
    let index = pivot + 1;
    while (l <= r) {
        l++
        if (nums[l] < nums[pivot]) {

            const temp = nums[l];
            nums[l] = nums[index];
            nums[index] = temp
            index++
        }
    }
    const temp = nums[pivot];
    nums[pivot] = nums[index-1];
    nums[index-1] = temp;

    return index -1;
}

这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。