「这是我参与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)
解法二
思路
堆排序。
- 我们可以建立一个大顶堆,依次往大顶堆里推入数字。
- 当大顶堆长度小于k时,直接推入。
- 当大顶堆长度不小于k时,判断当前元素和堆顶元素的大小,
- 如果当前元素小,则先将大顶堆堆顶元素弹出,然后将当前元素推入。
- 如果当前元素大,则跳过。
- 返回大顶堆的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个元素即可
代码后序再补上。