「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
这是一道排序问题,排序问题我们已经做过很多次了,可以说是非常的成熟了。
第一种方法,直接使用JavaScript函数实现。
var smallestK = function (arr, k) {
arr.sort((a, b) => a - b);
return arr.slice(0, k);
};
第二种方法,使用小顶堆实现。
var smallestK = function (arr, k) {
let heap = new Heap("small")
for (let i = 0; i < arr.length; i++) heap.push(arr[i])
let res = []
for (let i = 0; i < k; i++) res.push(heap.pop())
return res
};
小顶堆的实现我们在之前已经写过有很多次了,感兴趣的同学可以看我以前的文章。
同时我们也可以使用大顶堆,我们只保存我们需要的数量,如果超出了我们将堆顶元素出栈。
var smallestK = function (arr, k) {
let heap = new Heap("large")
for (let i = 0; i < arr.length; i++) {
heap.push(arr[i])
if (heap.size() > k) heap.pop()
}
let res = []
for (let i = 0; i < k; i++) {
res.push(heap.pop())
}
return res
};
快速排序
我们可以借鉴快速排序的思想。我们知道快排的划分函数每次执行完后都能将数组分成两个部分,小于等于分界值 pivot 的元素的都会被放到数组的左边,大于的都会被放到数组的右边,然后返回分界值的下标。与快速排序不同的是,快速排序会根据分界值的下标递归处理划分的两侧,而这里我们只处理划分的一边。
我们定义函数 randomized_selected(arr, l, r, k) 表示划分数组 arr 的 [l,r] 部分,使前 k 小的数在数组的左侧,在函数里我们调用快排的划分函数,假设划分函数返回的下标是 pos(表示分界值 pivot 最终在数组中的位置),即 pivot 是数组中第 pos - l + 1 小的数,那么一共会有三种情况:
情况:
- 如果 pos - l + 1 == k,表示 pivot 就是第 kkk 小的数,直接返回即可;
- 如果 pos - l + 1 < k,表示第 kkk 小的数在 pivot 的右侧,因此递归调用 randomized_selected(arr, pos + 1, r, k - (pos - l + 1));
- 如果 pos - l + 1 > k,表示第 kkk 小的数在 pivot 的左侧,递归调用 randomized_selected(arr, l, pos - 1, k)。
函数递归入口为 randomized_selected(arr, 0, arr.length - 1, k)。在函数返回后,将前 k 个数放入答案数组返回即可。
var smallestK = function(arr, k) {
randomizedSelected(arr, 0, arr.length - 1, k);
return arr.slice(0, k);
}
const randomizedSelected = (arr, l, r, k) => {
if (l >= r) {
return;
}
const pos = randomizedPartition(arr, l, r);
const num = pos - l + 1;
if (k === num) {
return;
} else if (k < num) {
randomizedSelected(arr, l, pos - 1, k);
} else {
randomizedSelected(arr, pos + 1, r, k - num);
}
}
// 基于随机的划分
const randomizedPartition = (nums, l, r) => {
const i = parseInt(Math.random() * (r - l + 1)) + l;
swap(nums, r, i);
return partition(nums, l, r);
}
const partition = (nums, l, r) => {
const pivot = nums[r];
let i = l - 1;
for (let j = l; j <= r - 1; ++j) {
if (nums[j] <= pivot) {
i = i + 1;
swap(nums, i, j);
}
}
swap(nums, i + 1, r);
return i + 1;
}
const swap = (nums, i, j) => {
[nums[i], nums[j]] = [nums[j], nums[i]];
}