295. Find Median from Data Stream

13 篇文章 0 订阅
4 篇文章 0 订阅

题目描述

如何求数据流的中位数情况,这个数据流可以随时添加数据,此题要找出数据流的中位数,数据流由无序整数组成,并且数据流是在变化的。

数据流顺序是无序的,添加的顺序也是无序,但是求解是中位数的话就要保持有序的状态,所以基本
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.

For example,
[2,3,4], the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Design a data structure that supports the following two operations:

void addNum(int num) - Add a integer number from the data stream to the data structure.
double findMedian() - Return the median of all elements so far.

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2

思路

数据流的添加是无序的,如何保证添加进来后维持有序性问题,保持有序即可,这个是很重要的,因为如果添加进来不保持一致性的话,是很难求解的?

问题的关键点

使用什么样的数据结构解决这个问题是关键?
数组,二叉树, 优先级队列情况

解法一 传统数组

插入排序, 每次插入一个数,都保持有序,然后分为偶数和奇数情况, 但这样的情况,时间复杂度很大, 时间复杂度为 O(n ^2) , 插

树(二分查找树,AVL树基本情况)

一般情况下时间复杂度为Olog(N), 最坏的情况下是O(N),二分查找树退化为一个链表的基本情况,这个是比较最坏的情况

堆维持有序性

堆Heap,将进来的数据流分成大小两个堆,分别保存堆的前半部分和后半部分,大堆是最小数字在堆顶,小堆是最大数在堆顶。取中间值时,根据两个堆的数字个数,如果个数相同,就各取堆顶数字取平均数就是中间数,如果有一个堆数字多1,中间数就是这个堆顶数字。

------- 堆一堆顶(最大数 m1) 堆二堆顶(最小数m2) ----------维持了中间的结构,

如何从堆中求得中位数情况

根据两个堆中元素的数目情况来定做吧,如果是总数是偶数, 如果是奇数

  1. left.size()== right.size()
    return (left.peek()+ right.peek())/2
  2. left.size() = right.size()+1
    return right.peek()
  3. left.size()+1 = right.size
    return left.peek();

如果选择哪个堆进行加入是关键

也可以优化成为这种方式

 if(maxHeap.isEmpty() || num <= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }

加入的方式

  1. 如果是第一次加入,也就是left 为空的, left.offer(num)
  2. 如果不是第一加入, 分为两种可能性
    1. 如果num>left.peek() ,那肯定是right.offer(num)
    2. 否则left.offer(num)

加入后如何维持两个堆的平衡

这个步骤确保从堆顶取出来的元素可以直接计算出数据流中的中位数情况
所以这两个堆中的数组保持如下的关系

  1. 数组相同
  2. left 比right多一个
  3. right 比left 多一个元素

为了维护两个树的关系,就像AVL树,我们需要进行调整

如果进行调整

在num加入两个堆中的一个后,判断他们之间的数目关系

  1. 如果左边的多于右边的

    1. 将左边的头元素加入右边
  2. 返过来是一样的效果情况

// 两边堆的数组大小相差个数,就是元素个数情况,最多差一个
        if(left.size()>right.size()+1){
            right.offer(left.poll());
        }
        if(right.size()>left.size()+1){
            left.offer(right.poll());
        }

如何维持两个堆的数目关系

为了保持中位数很容易算出来,跟堆顶的元素相关,无论是数据流中总的数组是多少,奇数还是偶数情况,都应该

  1. left.size()== right.size()
    return (left.peek()+ right.peek())/2
  2. left.size() = right.size()+1
    return right.peek()
  3. left.size()+1 = right.size
    return left.peek();

Java:
所以第一个该考虑的问题是选择什么样的数据结构, 当加入一个大小不知道的数据时,和前面的数据一样保持有序的状态即可

如何设计这个堆是个关键问题,设计成为两个堆,两个堆中的元素连接起来构成一个数组的形式,
第一个堆设计成为最大堆,堆顶元素为最大值,
第二个堆设计成为最小堆的形式,堆定元素最小值的形式,加入一个数组

成型代码

class MedianFinder {
    private PriorityQueue<Integer> left, right;
    /** initialize your data structure here. */
    
    public MedianFinder() {
        left = new PriorityQueue<>(new Comparator<Integer>()
        {
            @Override
            public int compare(Integer o1, Integer o2){
                   return o2-o1;}
        });
            right = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        //代码逻辑如何进行设计
         //第一次插入该种情况,肯定是在左边的情况进行
            if(left.size()==0)
            {
                left.offer(num);
            }
        // 第二次插入的情况, 判断是加在什么位置信息处
          else {
              if(num>left.peek())
              {
                    right.offer(num);
              }
              else 
                  left.offer(num);
          }
        //对添加后的长度进行修改即可,就类似AVL树,进行各种旋转, 保持整个数组平衡,,
        
    // 两边堆的数组大小相差个数,就是元素个数情况,最多差一个
        if(left.size()>right.size()+1){
            right.offer(left.poll());
        }
        if(right.size()>left.size()+1){
            left.offer(right.poll());
        }
    }

    
    
    public double findMedian() {
        //如果获取堆顶的元素进行计算该种情况
        if(left.size()==right.size())
        {
            return  (left.peek()+right.peek())/2.0;
        }
        else
            return left.size()>right.size() ? left.peek(): right.peek();
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

时间复杂度分析

O(log(N)) 调整堆中元素,O(1) 是进行计算

题目拓展

海量数据中如何寻找中位数?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要找出数据流的中位数,可以使用两个优先队列(堆)来实现。一个小顶堆存储较大的一半数据,一个大顶堆存储较小的一半数据。 具体步骤如下: 1. 初始化两个堆,一个小顶堆 `minHeap` 和一个大顶堆 `maxHeap`。 2. 遍历数据流中的每个元素: - 如果 `minHeap` 和 `maxHeap` 的大小相等,将元素插入到 `maxHeap` 中。 - 如果 `minHeap` 的大小大于 `maxHeap`,将元素插入到 `minHeap` 中。 - 如果插入元素后,`minHeap` 的堆顶元素大于 `maxHeap` 的堆顶元素,则交换两个堆顶元素。 3. 如果两个堆的大小之和是偶数,中位数就是两个堆顶元素的平均值;如果是奇数,中位数就是 `minHeap` 的堆顶元素。 下面是使用 C++ 实现的代码示例: ```cpp #include <iostream> #include <queue> #include <vector> class MedianFinder { public: void addNum(int num) { if (minHeap.empty() || num > minHeap.top()) { minHeap.push(num); } else { maxHeap.push(num); } if (minHeap.size() > maxHeap.size() + 1) { maxHeap.push(minHeap.top()); minHeap.pop(); } else if (maxHeap.size() > minHeap.size()) { minHeap.push(maxHeap.top()); maxHeap.pop(); } } double findMedian() { if (minHeap.size() == maxHeap.size()) { return (minHeap.top() + maxHeap.top()) / 2.0; } else { return minHeap.top(); } } private: std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; std::priority_queue<int, std::vector<int>, std::less<int>> maxHeap; }; int main() { MedianFinder finder; finder.addNum(1); finder.addNum(2); std::cout << finder.findMedian() << std::endl; // 输出 1.5 finder.addNum(3); std::cout << finder.findMedian() << std::endl; // 输出 2 return 0; } ``` 这段代码创建了一个 `MedianFinder` 类,通过 `addNum` 方法添加数据,然后通过 `findMedian` 方法获取中位数。在示例中,数据流为 1、2、3,所以中位数依次为 1.5 和 2。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值