[路飞] 最小 k 个数

138 阅读1分钟

题目描述

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

解题思路

算法

快排 partition

思路

我们通过快排 partition 的思想,把数组划分成最左面为 k 长度的部分,那么根据快排可得,划分的各个部分是相对有序的,因此这部分最左边的,长度为 k 的部分,是最小 k 个数,我们 slice 之后返回即可

过程

利用快排的 partition 思想,找到一个 pivot(这里是随机数),将数组从 l(default to 0) 至 r(default to length - 1) 进行划分,要注意一点的是,我们只会对左区间进行分割,因此要找的是最小 k。

每次 partition 完成之后,记录下来区间的最右边索引 pos,此时:

pos - l + 1 是我已经划分的区间长度,

根据不同的划分情况,我们需要在下次 partition 中传不同的 l, r

如果 > k,改变 rpos - 1,因此划分出来的长度已经超过了 k,所以我们减小范围,在更小的区间内划分

如果 < k,改变 lpos + 1, 长度不够,还要继续划分,所以剩余需要划分的长度为 k - (pos - l + 1)

代码

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

function select(arr, l, r, k) {
    if (l >= r) return

    const pos = partition(arr, l, r)
    const num = pos - l + 1
    if (num === k) return
    else if (num > k) {
        return select(arr, l, pos - 1, k)
    } else {
        return select(arr, pos + 1, r, k - num)
    }
}

function partition(arr, l, r) {
    const index = parseInt(Math.random() * (r - l + 1)) + l
    ;[arr[r], arr[index]] = [arr[index], arr[r]]
    const pivot = arr[r]

    let i = l - 1
    for (let j = l; j < r; j++) {
        if (arr[j] <= pivot) {
            i++
            ;[arr[i], arr[j]] = [arr[j], arr[i]]
        }
    }

    ;[arr[r], arr[i + 1]] = [arr[i + 1], arr[r]]
    return i + 1
}