题目
随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例1
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
题解
大根堆、小根堆
题目要求找中位数
,中位数就是数组中间的数;
理想的数据结构是使用两个数组left和right
-
left数组和right数组长度绝对值之差最大为1;
-
left数组最大值小于right最小值;
-
left降序排列
-
right升序排列
-
则有left[0]+right[0] / 2为数组中位数
如何构建理想数据结构呢?
大根堆、小根堆;如果不熟序可以跳转此处
大根堆(left):因为left中需要最大值
小根堆(right):在right中我们需要最小值
任意输入一个数x;
- 如果大根堆(left)数据中为空,直接放入大根堆
- 如果大根堆数据不为空,判断数据是否小于大根堆最大值?是,将数据塞入大根堆,否则塞入小根堆
- 获取大根堆小根堆数据长度,两者长度绝对值不超过1,超过1将大根堆中最大值移入小根堆中
任意时刻输出中位置,在大根堆,小根堆中寻找答案即可;时间复杂度为O(n)
代码
/**
* initialize your data structure here.
*/
var MedianFinder = function () {
this.minHeap = new Heap((a, b) => a < b)
this.maxHeap = new Heap((a, b) => a > b)
}
/**
* @param {number} num
* @return {void}
*/
MedianFinder.prototype.addNum = function (num) {
if (this.maxHeap.isEmpty()) {
this.maxHeap.push(num)
} else {
if (this.maxHeap.top() > num) {
this.maxHeap.push(num)
} else {
this.minHeap.push(num)
}
let maxSize = this.maxHeap.getSize()
let minSize = this.minHeap.getSize()
if (minSize > maxSize) {
let top = this.minHeap.pop()
this.maxHeap.push(top)
} else if (maxSize - 1 > minSize) {
let top = this.maxHeap.pop()
this.minHeap.push(top)
}
}
}
/**
* @return {number}
*/
MedianFinder.prototype.findMedian = function () {
let maxSize = this.maxHeap.getSize()
let minSize = this.minHeap.getSize()
if (maxSize === minSize) {
return (this.maxHeap.top() + this.minHeap.top()) / 2
} else {
return this.maxHeap.top()
}
}
class Heap {
constructor(compare) {
this.list = [0]
this.compare = typeof compare === 'function' ? compare : this.defaultCompare
}
defaultCompare(a, b) {
return a < b
}
swap(x, y) {
const t = this.list[x]
this.list[x] = this.list[y]
this.list[y] = t
}
isEmpty() {
return this.num === 0
}
getSize() {
return this.list.length - 1
}
top() {
return this.list[1]
}
left(x) {
return 2 * x
}
right(x) {
return 2 * x + 1
}
parent(x) {
return Math.floor(x / 2)
}
push(val) {
// 新增数据,向堆尾添加
this.list.push(val)
this.up(this.list.length - 1)
}
// 上浮
up(k) {
const { list, parent, compare } = this
while (k > 1 && compare(list[k], list[parent(k)])) {
this.swap(parent(k), k)
k = parent(k)
}
}
pop() {
const { list } = this
if (list.length === 0) return null
this.swap(1, list.length - 1)
const top = list.pop()
this.down(1)
return top
}
down(k) {
let { list, compare, left, right } = this
let size = this.getSize()
// 如果沉到堆底,就沉不下去了
while (left(k) <= size) {
let _left = left(k)
if (right(k) <= size && compare(list[right(k)], list[_left])) {
_left = right(k) // 选择左右子节点中更靠近堆顶的,这样能维持下沉后原本的 left与right 之间的顺序关系
}
// 如果当前的k比子节点更靠近堆顶,不用下沉了
if (compare(list[k], list[_left])) return
// 下沉
this.swap(k, _left)
k = _left
}
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* var obj = new MedianFinder()
* obj.addNum(num)
* var param_2 = obj.findMedian()
*/