leetcode连续中值

面试题 17.20. 连续中值

题目

随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[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()
 */

你可能感兴趣的:(leetcode,算法,职场和发展)