返回

JavaScript 中解决 LeetCode 295 数据流中位数的暴力、二分、堆栈方法

前端

在数据流中,随着新元素的不断加入,实时求取中位数是一项常见的任务。本文将探讨使用 JavaScript 语言解决 LeetCode 295 数据流中位数的三种方法:暴力法、二分法和堆栈法。

暴力法

暴力法是通过将数据流中的所有元素排序,然后根据元素的数量来计算中位数。对于包含 n 个元素的数据流,暴力法的时间复杂度为 O(n log n),因为它需要对数据流进行排序。

class MedianFinder {
  constructor() {
    this.arr = [];
  }

  addNum(num) {
    this.arr.push(num);
    this.arr.sort((a, b) => a - b);
  }

  findMedian() {
    const n = this.arr.length;
    if (n % 2 === 0) {
      return (this.arr[n / 2 - 1] + this.arr[n / 2]) / 2;
    } else {
      return this.arr[Math.floor(n / 2)];
    }
  }
}

二分法

二分法可以优化暴力法的时间复杂度,将其降低到 O(log n)。它通过使用二分法在排序后的数据流中查找中位数,从而避免了对整个数据流进行排序。

class MedianFinder {
  constructor() {
    this.arr = [];
  }

  addNum(num) {
    const idx = this.findInsertionPoint(num);
    this.arr.splice(idx, 0, num);
  }

  findInsertionPoint(num) {
    let left = 0;
    let right = this.arr.length;
    while (left < right) {
      const mid = Math.floor((left + right) / 2);
      if (this.arr[mid] < num) {
        left = mid + 1;
      } else {
        right = mid;
      }
    }
    return left;
  }

  findMedian() {
    const n = this.arr.length;
    if (n % 2 === 0) {
      return (this.arr[n / 2 - 1] + this.arr[n / 2]) / 2;
    } else {
      return this.arr[Math.floor(n / 2)];
    }
  }
}

堆栈法

堆栈法利用最小堆和最大堆来维护数据流的中位数。最小堆存储较大的元素,而最大堆存储较小的元素。中位数可以通过这两个堆的顶元素来计算。使用堆栈法,添加新元素的时间复杂度为 O(log n),而查找中位数的时间复杂度为 O(1)。

class MedianFinder {
  constructor() {
    this.maxHeap = new MaxHeap();
    this.minHeap = new MinHeap();
  }

  addNum(num) {
    if (this.maxHeap.size === 0 || num <= this.maxHeap.peek()) {
      this.maxHeap.insert(num);
    } else {
      this.minHeap.insert(num);
    }
    this.rebalanceHeaps();
  }

  rebalanceHeaps() {
    while (this.maxHeap.size > this.minHeap.size) {
      this.minHeap.insert(this.maxHeap.poll());
    }
    while (this.minHeap.size > this.maxHeap.size + 1) {
      this.maxHeap.insert(this.minHeap.poll());
    }
  }

  findMedian() {
    if (this.maxHeap.size === this.minHeap.size) {
      return (this.maxHeap.peek() + this.minHeap.peek()) / 2;
    } else {
      return this.maxHeap.size > this.minHeap.size ? this.maxHeap.peek() : this.minHeap.peek();
    }
  }
}

class Heap {
  constructor() {
    this.heap = [];
  }

  get size() {
    return this.heap.length;
  }

  peek() {
    return this.heap[0];
  }

  insert(num) {
    this.heap.push(num);
    this.heapifyUp();
  }

  poll() {
    const root = this.heap[0];
    this.heap[0] = this.heap.pop();
    this.heapifyDown();
    return root;
  }

  heapifyUp() {
    let current = this.heap.length - 1;
    while (current > 0) {
      const parent = Math.floor((current - 1) / 2);
      if (this.compare(this.heap[current], this.heap[parent]) > 0) {
        this.swap(current, parent);
        current = parent;
      } else {
        break;
      }
    }
  }

  heapifyDown() {
    let current = 0;
    while (current < this.heap.length) {
      const left = 2 * current + 1;
      const right = 2 * current + 2;
      let largest = current;
      if (left < this.heap.length && this.compare(this.heap[left], this.heap[current]) > 0) {
        largest = left;
      }
      if (right < this.heap.length && this.compare(this.heap[right], this.heap[largest]) > 0) {
        largest = right;
      }
      if (largest !== current) {
        this.swap(current, largest);
        current = largest;
      } else {
        break;
      }
    }
  }

  compare(a, b) {
    return a - b;
  }

  swap(a, b) {
    const temp = this.heap[a];
    this.heap[a] = this.heap[b];
    this.heap[b] = temp;
  }
}

class MaxHeap extends Heap {
  compare(a, b) {
    return b - a;
  }
}

class MinHeap extends Heap {
  compare(a, b) {
    return a - b;
  }
}

结论

解决 LeetCode 295 数据流中位数问题有三种方法:暴力法、二分法和堆栈法。每种方法都有其优缺点,在不同的场景下可能更适用。暴力法简单易懂,但效率较低。二分法提高了效率,但需要维护排序后的数据流。堆栈法具有最高的效率和最快的查找中位数时间,但实现难度稍高。选择哪种方法取决于具体需求和时间/空间复杂度要求。