开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20天,点击查看活动详情
题目
剑指 Offer 40. 最小的k个数
输入整数数组
arr,找出其中最小的k个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
分析
简单粗暴两步完成
- 数组排序
- 数组截取
实现
function getLeastNumbers(arr: number[], k: number): number[] {
arr.sort((a, b) => a - b)
return arr.slice(0, k)
};
题目
剑指 Offer 41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
示例:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
分析一
先简单粗暴的做出来试试
由于该数据结构没有要求需要记录数据进入的顺序,故我们可以在内部将数据进行排序
那么这道题的求解可以分解为
-
添加元素,并进行排序
-
求中位数,分两种情况(偶数和奇数数组)
- 偶数数组:数组长度 / 2 为较大的中位数索引,减1得出较小中位数索引,可得
(this.list[Math.floor(len / 2)] + this.list[Math.floor(len / 2) - 1]) / 2- 奇数数组:数组长度 / 2 取整数部分就是中位数索引,可以直接求得
this.list[Math.floor(len / 2)]
实现
class MedianFinder {
list: number[]
constructor() {
this.list = []
}
addNum(num: number): void {
this.list.push(num)
this.list.sort((a, b) => a - b)
}
findMedian(): number {
let len = this.list.length
// console.log(len)
if (len % 2 == 0) {
let index = Math.floor(len / 2)
return (this.list[index] + this.list[index - 1]) / 2
} else {
let index = Math.floor(len / 2)
return this.list[index]
}
}
}
补充
JS堆的实现
在计算机科学中,堆是一种数据结构,它是一种特殊的树形结构,通常用来实现优先队列。堆具有以下特性:
- 堆是一棵完全二叉树,即除了最后一层,其他层的节点都是满的,最后一层的节点从左到右排列。
- 堆中每个节点都必须满足堆属性,即父节点的键值必须大于或等于(或小于或等于)其子节点的键值。
- 堆通常分为最大堆和最小堆。在最大堆中,父节点的键值大于或等于其子节点的键值;在最小堆中,父节点的键值小于或等于其子节点的键值。
堆的主要应用是实现优先队列,其中每个元素都有一个关键字,优先级高的元素先出队。堆的时间复杂度通常是O(log n),因此在处理大量数据时非常高效。堆还可以用于排序算法,例如堆排序。
最大堆代码
class MaxHeap {
constructor() {
this.heap = [];
}
// 获取父节点索引
getParentIndex(index) {
return Math.floor((index - 1) / 2);
}
// 获取左子节点索引
getLeftChildIndex(index) {
return index * 2 + 1;
}
// 获取右子节点索引
getRightChildIndex(index) {
return index * 2 + 2;
}
// 上移操作,用于插入节点后调整堆
shiftUp() {
let index = this.heap.length - 1;
while (index > 0) {
let parentIndex = this.getParentIndex(index);
if (this.heap[index] > this.heap[parentIndex]) {
[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
index = parentIndex;
} else {
break;
}
}
}
// 下移操作,用于删除节点后调整堆
shiftDown() {
let index = 0;
while (this.getLeftChildIndex(index) < this.heap.length) {
let maxChildIndex = this.getLeftChildIndex(index);
let rightChildIndex = this.getRightChildIndex(index);
if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] > this.heap[maxChildIndex]) {
maxChildIndex = rightChildIndex;
}
if (this.heap[index] < this.heap[maxChildIndex]) {
[this.heap[index], this.heap[maxChildIndex]] = [this.heap[maxChildIndex], this.heap[index]];
index = maxChildIndex;
} else {
break;
}
}
}
// 插入节点
insert(value) {
this.heap.push(value);
this.shiftUp();
}
// 删除堆顶节点
delete() {
if (this.heap.length === 0) {
return null;
}
if (this.heap.length === 1) {
return this.heap.pop();
}
let deletedValue = this.heap[0];
this.heap[0] = this.heap.pop();
this.shiftDown();
return deletedValue;
}
}
以上代码中,我们定义了一个 MaxHeap 类,它包含了以下方法:
getParentIndex(index):获取父节点索引。getLeftChildIndex(index):获取左子节点索引。getRightChildIndex(index):获取右子节点索引。shiftUp():上移操作,用于插入节点后调整堆。shiftDown():下移操作,用于删除节点后调整堆。insert(value):插入节点。delete():删除堆顶节点。
在这个示例中,我们实现了最大堆。如果需要实现最小堆,只需要修改 shiftUp() 和 shiftDown() 方法中的比较符号即可。