剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
一.直接操作数组方法
解题思路:定义一个新的数组ans,用来接受最小的k个数,当ans长度大于k时,就去遍历ans将里面最大值的索引找出来,然后根据索引删除最大值,剩下的就是k个最小值,代码如下:
var getLeastNumbers = function(arr, k) {
let ans = [];
let index = 0;
for(let i = 0; i < arr.length; i ++){
ans.push(arr[i]);
if(ans.length > k) {
index = getMax(ans);
ans.splice(index,1);
}
}
return ans;
};
var getMax = function(arr){
if(!arr) return [];
let maxVal = arr[0];
let maxLeng = 0;
for(let i = 1; i < arr.length; i ++){
if(arr[i] > maxVal) {
maxVal = arr[i];
maxLeng = i;
}
}
return maxLeng;
}
二.通过大顶堆实现
堆的特性:
-
必须是[完全二叉树]
完全二叉树特性:
1.根编号为i
左子节点编号:2 * i
右子节点编号:2 * i + 1
2.可以用连续控件存储的数组 -
用数组实现
-
堆是优先队列的实现方式,优先队列是堆的别名
-
任一结点的值是其子树所有结点的大顶堆或小顶堆
- 任意三角区域根节点大于子节点,也称大顶堆;
- 任意三角区域根节点小于子节点,也称小顶堆。 数据结构定义:定义一种性质,并且维护这种性质 解题思路:我们知道队列是头部出队,尾部入队操作,因为这题求的是最小k个数,所以我们可以根据大顶堆的特性,删除根节点,留下来的都是小值了,每次不管是入队还是出队都要保持大顶堆的要求,入队的时候需要向上调整,维持大顶堆的特性,向上调整的方法是,跟根节点比较,如果大于根节点就与根节点互换值,依次比较直到根节点,根节点的索引等于当前节点减一除2,删除节点的时候我们可以直接尾部节点赋值给根节点保持树结构完整性,然后向下调整,调整方法是从跟节点开始与子节点最大的那个比较,然后互换值,代码如下:
var getLeastNumbers = function(arr, k) {
if(k == 0) return [];
//重置保存最小k个数的数组
data = [];
//重置索引从0开始
cnt = 0;
//遍历arr数组,向data加数据
for(let i = 0; i < arr.length; i ++){
//当data数组已经保存k的节点时,就将当前数与大顶堆根节点对比,如果大那就不用加入了,如果小,就先加入再删去根节点
if(data.length == k){
if(arr[i] < data[0]){
push_up(arr[i]);
pop_down();
}
}else{
push_up(arr[i]);
}
}
return data;
};
let data = [],cnt = 0;
var push_up = function(val){
//入队
data[cnt] = val;
let idx = cnt;//当前节点
let rootIndex = Math.floor((idx - 1)/2);//根节点
while(idx && data[idx] > data[rootIndex]){
//新加入的节点大于根节点就与根节点互换向上调整
swpa(data[rootIndex],rootIndex,data[idx],idx);
//重置当前节点
idx = rootIndex;
//重置根节点
rootIndex = Math.floor((idx - 1)/2);
}
cnt ++;
return ;
}
var pop_down = function(){
if(data.length <= 1) {
data = [];
return ;
}
data[0] = data.pop();
cnt --;
let n = cnt - 1;//总结点个数
let idx = 0;//当前节点
let sonIndex = 2*idx + 1;//左子节点
//sonIndex 小于n说明存在左子节点
while( sonIndex <= n){
let temp = idx;//记录最大值的索引
//根节点与左子节点比较,谁大就将谁的索引赋值给temp
if(data[idx] < data[sonIndex]) temp = sonIndex;
//判断右子节点是否存在,存在就跟当前最大值比较,谁大就将索引赋值给temp
if( sonIndex + 1 <= n && data[temp] < data[sonIndex + 1] ) temp = sonIndex + 1;
//如果当前节点就是最大值,直接返回不用调整
if(temp == idx) break;
//到了这里就说明根节点不是最大的需要调整
swpa(data[temp],temp,data[idx],idx);
//重置当前节点
idx = temp;
//重置当前节点左子节点
sonIndex = 2*idx + 1;
}
return ;
}
//数组两个值互换
var swpa = function(v,i,v2,i2){
data[i] = v2;
data[i2] = v;
}