The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.
arr = [2,3,4]
, the median is 3
.arr = [2,3]
, the median is (2 + 3) / 2 = 2.5
.Implement the MedianFinder
class:
MedianFinder()
initializes the MedianFinder
object.void addNum(int num)
adds the integer num
from the data stream to the data structure.double findMedian()
returns the median of all elements so far. Answers within 10-5
of the actual answer will be accepted.Example:
Input
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
Output
[null, null, null, 1.5, null, 2.0]
Explanation
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
Constraints:
-105 <= num <= 105
findMedian
.5 * 104
calls will be made to addNum
and findMedian
.Follow up:
[0, 100]
, how would you optimize your solution?[0, 100]
, how would you optimize your solution?One straightforward approach is to maintain a sorted list of all numbers seen so far. When addNum
is called, we insert the new number into the correct position in the sorted list. When findMedian
is called, we simply return the middle element (or the average of the two middle elements) of the list.
Code (Python):
class MedianFinder:
def __init__(self):
self.data = []
def addNum(self, num: int) -> None:
# Insert num into self.data while maintaining sorted order
if not self.data:
self.data.append(num)
return
for i in range(len(self.data)):
if num < self.data[i]:
self.data.insert(i, num)
return
self.data.append(num)
def findMedian(self) -> float:
n = len(self.data)
if n % 2 == 0:
return (self.data[n // 2 - 1] + self.data[n // 2]) / 2.0
else:
return float(self.data[n // 2])
Time Complexity:
addNum
: O(n), because inserting into a sorted list of size n takes O(n) time in the worst case.findMedian
: O(1), because accessing the middle element(s) takes constant time.Space Complexity:
A better approach is to use two heaps: a max-heap and a min-heap.
We maintain the invariant that the size of the max-heap is always equal to or one greater than the size of the min-heap. This ensures that when the total number of elements is odd, the median is simply the top of the max-heap. When the total number of elements is even, the median is the average of the tops of the two heaps.
Algorithm:
addNum(num)
:
findMedian()
:
Code (Python):
import heapq
class MedianFinder:
def __init__(self):
self.small = [] # max-heap (negated values)
self.large = [] # min-heap
def addNum(self, num: int) -> None:
heapq.heappush(self.small, -num)
heapq.heappush(self.large, -heapq.heappop(self.small))
if len(self.large) > len(self.small):
heapq.heappush(self.small, -heapq.heappop(self.large))
def findMedian(self) -> float:
if len(self.small) > len(self.large):
return -self.small[0]
else:
return (-self.small[0] + self.large[0]) / 2.0
Time Complexity:
addNum
: O(log n), because heap push and pop operations take O(log n) time.findMedian
: O(1), because accessing the top of a heap takes constant time.Space Complexity:
findMedian
will only be called after at least one number has been added. Thus, no special handling is needed for an empty stream.addNum
and findMedian
, but the space complexity is O(1) since the array size is fixed.