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.
Examples:
[2,3,4], the median is3
[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.
The Idea: Maintain two priority queues that are the same size (+- 1), and front of each queue represents the continuation from one to another list.
The mid point will follow to be the average of from of both queues when the sizes are the same, or the front of the larger queue otherwise. The biggest challenge of this problem is the maintenance of both queues. The comments in the code explain it in more detail, but what is comes down to is recognizing which queue a number belong to, and then if needed, making the necessary adjustments to maintain the same queue size,
Complexity: O(1) findMedian, O(log(n)) addNum, where n is the total number of elements inserted
from queue import PriorityQueueclassmyPriorityQueue(PriorityQueue):def__init__(self):super(PriorityQueue, self).__init__()deffront(self):if self.empty():raiseIndexError('Priority Queue is empty')return self.queue[0]classMaxPriorityQueue(PriorityQueue):def__init__(self):super(PriorityQueue, self).__init__()defput(self,item,block=True,timeout=None):super().put(-item)defget(self,block=True,timeout=None):return-super().get()deffront(self):if self.empty():raiseIndexError('Priority Queue is empty')return-self.queue[0]classMedianFinder:def__init__(self):""" initialize your data structure here. """ self.large_half =myPriorityQueue() self.small_half =MaxPriorityQueue()defaddNum(self,num):""" :type num: int :rtype: void """ifnot self.small_half.empty()andnot self.large_half.empty():# if the sizes are the same, then put the number to the group is belongs toif self.small_half.qsize()== self.large_half.qsize():# swap the elements if they are in the incorrect groupif self.small_half.front()> self.large_half.front(): self.small_half.put(self.large_half.get()) self.large_half.put(self.small_half.get())if num > self.small_half.front(): self.large_half.put(num)else: self.small_half.put(num)else:# if the larger half has more elements than smaller halfif self.small_half.qsize()< self.large_half.qsize():# if the number belongs in the larger half, we need to trade elements with the smaller halfif num > self.small_half.front(): self.small_half.put(self.large_half.get()) self.large_half.put(num)else: self.small_half.put(num)# otherwise the logic gets reversed. there are more elements in the smaller halfelse:# which means that if the number belongs in the larger half, we are free to put it# in to make both equal sizeif num > self.small_half.front(): self.large_half.put(num)# otherwise the number belong to smaller half, which means were going to have to trade elementselse: self.large_half.put(self.small_half.get()) self.small_half.put(num)else: self.small_half.put(num)if self.small_half.empty()\else self.large_half.put(num)deffindMedian(self):""" :rtype: float """if self.large_half.qsize()> self.small_half.qsize():return self.large_half.front()elif self.large_half.qsize()== self.small_half.qsize():return (self.large_half.front()+ (self.small_half.front())) /2else:return self.small_half.front()
Limited Testing
# TEST: ascendingobj1 =MedianFinder()for i inrange(10): obj1.addNum(i)print(obj1.findMedian())print('')# TEST: descendingobj2 =MedianFinder()for i inrange(10,-1,-1): obj2.addNum(i)print(obj2.findMedian())print('')# TEST: mixobj3 =MedianFinder()for l in [range(1,4),range(6,3,-1)]:for i in l: obj3.addNum(i)print(obj3.findMedian())print('')