这个题看完题目我就知道我会写,但是因为对题目理解不到位,多走了弯路,最小的k个数,最开始没有考虑重复,然后考虑到重复以后 觉得最小的k个数不算重复,这样还是不对,比如 [0,1,1,2]最小的2个书 答案是[0,1],我觉得是[0,1,1]这样就打出来的多了.还有一点儿就是sort这个方法不是很清楚之前 默认不传值,按照第一位的数字大小排序,必须处理一下
这个题,常规思想比较简单,但是运行时间和占用内存都比较大啊,所以我看了其他解析,发现常用的还有两种方法,我就又学习了一种更优的解析.快速排序,有几种写法,我都看不懂,找了个我感觉好理解的代码,如下
方法一
// 快排思维
// 每次从[start, end]范围内的数组中随机选择一个标杆元素(代码里取得是第一个元素),
// 然后把数组中所有小于标杆的放在数组左边,所有大于标杆的元素放在数组右边,
// 然后判断标杆元素的位置是否等于目标位置。如果目标位置小于当前位置,则继续在左边查找,如果目标位置大于当前位置,则继续在右边查找。
// 这样每次迭代都会缩小查找的范围。最理想的情况下时间复杂度是 O(logN)
// 2, 8, 1, 1, 0, 11, -1, 0 如果k = 3 输出[-1,0,1]
var getLeastNumbers = function(arr, k) {
let len = arr.length;
if(!len || !k) return [];
// 定义最开始和最后两个位置的下标
let start = 0;
let end = len - 1;
// 寻找一次标杆元素的位置
let index = quikSort(arr, start, end);
// 如果标杆元素的位置不等k
while(index !== k - 1) {
// 标杆儿元素大于目标位置, 即目标位置小于标杆儿位置,则继续在左边查找
if(index > k - 1) {
end = index - 1;
} else {
start = index + 1;
}
index = quikSort(arr,start, end)
}
return arr.slice(0, index + 1)
};
// 2, 8, 1, 1, 0, 11, -1, 0 9 length8 如果k = 3 输出[-1,0,1]
// 然后把数组中所有小于标杆的放在数组左边,所有大于标杆的元素放在数组右边,
function quikSort(arr, left, right) { // left-0,right-7
let pivot = arr[left]; // d第一次是2
while(left < right) {
while(left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right];
while(left < right && arr[left] < pivot) {
left ++;
}
arr[right] = arr[left];
}
//
arr[left]=pivot;
return left;
}
方法二 最大堆
这个具体题解都写了
function swap(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
class MaxHeap {
// constructor(arr = []) {
// this.container = [];
// if (Array.isArray(arr)) {
// arr.forEach(this.insert.bind(this));
// }
// }
constructor(arr = []) {
// 初始化container 这里把空数组赋值
this.container = [];
// 把arr 一次入堆 判断有哦没有都行
// if (Array.isArray(arr)) {
arr.forEach(this.insert.bind(this));
// }
}
// 从底部插入
insert(data) {
const {container} = this;
container.push(data);
// 找到堆的最后一个元素 坐标
let index = container.length - 1;
while(index) {
// 找到当前坐标的父节点
let parent = Math.floor((index - 1) / 2);
// index 为左/右节点 如果不大于 父节点 就中止 因为最大堆父节点要比左/右节点大
if(container[index] <= container[parent]) {
break;
}
// 左/右节点大于父节点 就要和父节点调换位置
swap(container, index, parent);
// 为下一次while循环做准备 把父节点左右字节点就是往上移动
index = parent;
}
}
// 从顶部删除
extract(){
const {container} = this;
// 判空 没有的话就停止
if(!container.length) {
return null;
}
// 交换堆的 最后一个(length-1)和头一个0的位置
// swap(this.container, this.container.length - 1, 0)
swap(container, 0, this.container.length - 1)
//定义弹出去的值和弹出去以后container的长度 定义一个0元素和0元素的左子节点 2* + 1(先定义左子节点因为一定先有左子节点才会有右子节点)
const res = this.container.pop();
let len = this.container.length;
let index = 0;
let leftIndex = 2*index + 1;
// 左子节点比container的长度小 就进入while循环
while(leftIndex < len) {
// 若有右子节点并且右子节点比左子节点大 则把右子节点给左子节点
let rightInx = 2*index + 2;
if(rightInx<len && container[rightInx] > container[leftIndex]) {
leftIndex = rightInx;
}
// 左节点 如果不大于 父节点index 就中止 因为最大堆父节点要比左/右节点大
if(container[leftIndex] <= container[index]) {
break;
}
//交换左子节点和父节点的位置
swap(this.container, leftIndex, index);
// 为下一步循环做准备重置index和leftIndex的值 把左子节点的值付给父节点相当于整体下移动
index = leftIndex;
//然后重置左子节点
leftIndex = 2*index + 1;
}
return res;
}
// 头节点 大顶堆cotainer有值 就返回头部0元素 否则返回null
top() {
if(!this.container.length) return null;
return this.container[0]
}
}
var getLeastNumbers = function(arr, k) {
// 先写思路 求最小的k个数 则构建一个k个数的大顶堆, 最后求到数组中最小的k个数的大顶堆,因为顶堆元素就是大顶堆的最大值,所以比较剩下元素和堆顶元素,
// 判空写不写都行
// const length = arr.length;
// if (k >= length) {
// return arr;
// }
// 1 先以数组钱k个值 构造大顶堆
const heap = new MaxHeap(arr.slice(0,k));
// 2 循环剩下的元素做对比
for(let i = k; i < arr.length; i++) {
// 元素比顶堆元素小则替换,否则不变
if(arr[i] < heap.top()) {
// 弹出堆顶
heap.extract()
// 把元素入队
heap.insert(arr[i])
}
}
// 返回大顶堆
return heap.container;
};