一日一练:数据流中的第K大元素

本文介绍了两种方法来寻找数据流中的第k大元素:1. 使用最小堆,当堆内元素数量超过k时,替换堆顶元素并调整堆;2. 先对数组排序,然后通过二分查找确定新元素位置。这两种方法分别通过代码详细解释了其实现过程。

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

最小堆

如果将数据流中的前k大元素存储在最小堆中,因为堆顶是最小值,所以堆顶元素就是当前数据流中的第k大元素。

可以在之前最小堆的实现基础上扩展一下。👉 最小堆实现

之前的实现可以无限存储,这里可以定义一个最大长度

  • 当堆内长度小于最大长度时,还是按照之前的逻辑加入,同时对新加入元素做swim调整
  • 但是如果长度大于等于最大长度,就直接将堆顶元素换成新加入的元素,然后对堆顶做sink调整

具体代码:

const KthLargest = function(k, nums) {
    this.k = k;
    this.heap = new MinBinaryHeap(nums, k);    
};

KthLargest.prototype.add = function(val) {
    this.heap.push(val);
    return this.heap.peek();
};

class MinBinaryHeap {
  constructor(data, k) {
    this.s = []
    this.len = 0
    this.k = k
    // 对初始数据做 堆化
    this.heapify(data)
  }

  heapify(data) {
    for(let i = 0; i < data.length; i++) {
      this.push(data[i])
    }
  }

  push(el) {
    // 将总长度限制在k范围内
    if (this.len < this.k) {
      this.s[this.len] = el
      ++this.len
      if (this.len > 1) {
        this.swim(this.len - 1)
      }
    } else {
      if (this.s[0] >= el) {
        return
      }
      this.s[0] = el
      this.sink(0)
    }

  }

  pop() {
    if (this.len === 0) {
      return null
    }
    const top = this.s[0]

    if (this.len > 1) {
      this.s[0] = this.s[this.len - 1]
      this.sink(0)
    }
    this.len--
    return top
  }

  peek() {
    if (this.len === 0) {
      return null
    } else {
      return this.s[0]
    }
  }

  sink(idx) {
    const lastIdx = this.len - 1
    while (idx <= lastIdx) {
      const rightIdx = (idx + 1) * 2
      const leftIdx = rightIdx - 1
      if (leftIdx <= lastIdx) {
        let smallValueIdx = leftIdx
        if (rightIdx <= lastIdx && this.compare(smallValueIdx, rightIdx) > 0) {
          smallValueIdx = rightIdx
        }
        if (this.compare(idx, smallValueIdx) > 0) {
          this.swap(idx, smallValueIdx)
          idx = smallValueIdx
        } else {
          break
        }
      } else {
        break
      }
    }
  }

  swim(idx) {
    while (idx >= 0) {
      let parent = (idx - 1) >> 1
      if (this.compare(parent, idx) > 0) {
        this.swap(parent, idx)
        idx = parent
      } else {
        break
      }
    }
  }

  swap(a, b) {
    let temp = this.s[a]
    this.s[a] = this.s[b]
    this.s[b] = temp
  }

  compare(a, b) {
    return this.s[a] - this.s[b]
  }
}

排序 + 二分

这道题还有一种思路就先将数组排序,之后二分查找新数据应该插入的位置,然后取索引为k-1的值即为第k大的值

const KthLargest = function (k, nums) {
  this.k = k
  this.sorted = nums.sort((a, b) => b - a)
}

KthLargest.prototype.add = function (val) {
  let idx = this.findLessThan(val)
  // 插入对应的位置
  // splice对负数会反向查找
  if (idx <= -1) {
    this.sorted.unshift(val)
  } else if (idx >= this.sorted.length) {
    this.sorted.push(val)
  } else {
    this.sorted.splice(idx, 0, val)
  }
  return this.sorted[this.k - 1]
}

// 二分查找到 val 需要放入的索引
KthLargest.prototype.findLessThan = function (val) {
  let left = -1,
    right = this.sorted.length
  while (left < right) {
    let mid = left + ((right - left) >> 1)
    if (this.sorted[mid] >= val) {
      left = mid + 1
    } else {
      right = mid
    }
  }
  return right
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值