[路飞]连续中值

159 阅读3分钟

记录 1 道算法题

连续中值

leetcode-cn.com/problems/co…


要求:

    * 加入一个新的值时,重新计算中位数
    * 数组长度是偶数时,返回中间两个的平均值,奇数时,返回中间的数。
    

首先比较简单的做法就是每次推入到数组都做一次冒泡,保证顺序的准确。

    function MedianFinder() {
       this.data = []
     }

     MedianFinder.prototype.addNum = function (num) {
       const { data } = this
       data.push(num)
       // 得到最新的结尾下标
       let i = data.length - 1
       while (i > 0) {
           // 从后面开始进行冒泡,升序排列
         if (data[i] < data[i - 1]) {
           const temp = data[i]
           data[i] = data[i - 1]
           data[i - 1] = temp
         }
         i--
       }
     }

     MedianFinder.prototype.findMedian = function () {
       const { data } = this
       let i
       if (data.length === 0) return
       let len = data.length - 1
       // 用下标计算,如果下标不是偶数
       if (len % 2 !== 0) {
         i = len >> 1
         return (data[i] + data[i + 1]) / 2
       } else {
           // 下标为偶数
         i = len / 2
         return data[i]
       }
     }

暴力解做完了,接下来看看优化的写法是怎么样的。

优化的方向就是减少排序次数,当数组长度为奇数时,可以将这个中位数拆成第 n 大的数。当数组长度为偶数时,可以将这个中位数拆成第 n 大的数和第 k 小的数,就是把数组给折成两半,只需要知道他们的边界的数,其他位置的顺序可以忽略。

    [1,2,3,4] 中位数是 23 的平均数,
    即 第二小的数和第二大的数。
    
    [1,2,3] 中位数是 2,
    即 第二小的数或第二大的数。

为此我们需要一个堆,找最大第 n 个用最小堆,比较规则:升序排列。找最小第 n 个用最大堆,比较规则:降序排列。

为了让两个堆保持数量均等,采用数组长度是偶数的时候,往左边的堆(最大堆)放。数组长度是奇数时往右边的堆(最小堆)放。左边放一个,右边放一个。

添加的部分逻辑比较重,每次添加之前都要判断是否应该放在另一个堆更合适,如果是就先放进另一个堆,然后取出堆顶的值。用这个值再继续正常的左边放一个,右边放一个。

总是要保证左边的堆的堆顶要小于右边的堆的堆顶,不然后续添加会乱掉。

ps:堆的代码看这篇文章,这里直接使用

    function MedianFinder() {
        this.count = 0
        // 第 n 大找最小堆,升序排列,第 n 大其实是在n个数中最小的那个
        this.minHeap = new Heap((a, b) => a - b)
        // 第 n 小找最大堆,降序排列
        this.maxHeap = new Heap((a, b) => b - a)
      }

      MedianFinder.prototype.addNum = function (num) {
        if (this.count % 2 === 0) {
            // 应该添加到左边的堆
            // 和右边的堆顶比较,如果比右边堆顶还大说明一定比左边的都大,属于右边的堆。
          if (num > this.minHeap.data[0]) {
              // 存进去排序
            this.minHeap.push(num)
            // 再取出来
            const val = this.minHeap.pop()
            // 代替 num 添加到左边的堆。
            this.maxHeap.push(val)
          } else {
              // 如果比右边的堆顶小就可以之间加到左边的堆
            this.maxHeap.push(num)
          }
        } else {
            // 应该加入右边的堆
            // 比左边的堆顶小的时候,比右边堆任何一个都小,属于左边
          if (num < this.maxHeap.data[0]) {
              // 存进去排序
            this.maxHeap.push(num)
            // 再取出来
            const val = this.maxHeap.pop()
            // 代替 num 添加到右边的堆
            this.minHeap.push(val)
          } else {
              // 如果比左边的堆顶大,就直接添加到右边的堆
            this.minHeap.push(num)
          }
        }
        this.count++
      }

      MedianFinder.prototype.findMedian = function () {
          // 根据奇偶数取平均数还是直接取
        if (this.count % 2 === 0) {
          return (this.minHeap.data[0] + this.maxHeap.data[0]) / 2
        } else {
          return this.maxHeap.data[0]
        }
      }

结束