Skip to content

Commit c2256f8

Browse files
author
weiy
committed
find median from data stream hard
1 parent c89791a commit c2256f8

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

Diff for: Heap/FindMedianFromDataStream.py

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
"""
2+
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.
3+
4+
For example,
5+
[2,3,4], the median is 3
6+
7+
[2,3], the median is (2 + 3) / 2 = 2.5
8+
9+
Design a data structure that supports the following two operations:
10+
11+
void addNum(int num) - Add a integer number from the data stream to the data structure.
12+
double findMedian() - Return the median of all elements so far.
13+
14+
15+
Example:
16+
17+
addNum(1)
18+
addNum(2)
19+
findMedian() -> 1.5
20+
addNum(3)
21+
findMedian() -> 2
22+
23+
24+
Follow up:
25+
26+
If all integer numbers from the stream are between 0 and 100, how would you optimize it?
27+
If 99% of all integer numbers from the stream are between 0 and 100, how would you optimize it?
28+
29+
30+
找出数据流中的中位数。
31+
32+
这个中位数要求的是排序后的中位数。
33+
1.
34+
所以最暴力的方法即是:
35+
1. 每新加一个数据就放到列表尾,然后进行一次排序。
36+
1.1 当然如果用这个思路到是可以优化,用 O(n) 的搜索确定插入到哪里,插入到列表也是O(n)的操作。
37+
2. 随后输出 这一步到是 O(1)。
38+
没写这个的代码,太暴力...
39+
2.
40+
基于 1. 的改进,查找我们用二分来代替,这样查找可以降低到 O(log n),但插入仍然是 O(n)。
41+
42+
输出索引的话就是 O(1) 没什么好说的。
43+
44+
写了这个的思路,出乎意料的是直接跑赢了 97% 的代码,不过随后几次平均变得低了。
45+
能跑赢这么多的原因也无非是测试数据过小,小到几乎可以忽略 O(n) 的插入耗时。
46+
47+
这个代码的话很好写:
48+
一个用于放元素的列表:
49+
self.stream_data = []
50+
51+
def addNum(self, num):
52+
bisect.insort_right(self.stream_data, num)
53+
54+
def findMdian(self):
55+
'''
56+
判断奇偶。
57+
'''
58+
pass
59+
3.
60+
Discuss 里的思路基本是用到了堆。这个技巧感觉很棒:
61+
堆的插入和查询都是 O(log n) 级别的,用堆即可克服列表插入的 O(n) 的耗时情况:
62+
基本骨架是:
63+
如果我们将一个数据分成左右两部分,那么左边最大和右边最小即为我们找中位的基础元素。
64+
left right
65+
[1,2,3, 4,5,6]
66+
67+
左边这个符合大顶堆,右边这个则是小顶堆。
68+
69+
1. 建立一大一小两个堆:
70+
大顶堆用于存放左边的元素。
71+
小顶堆用于存放右边的元素。
72+
2. 要面对的问题是如何在新数据来临时插入:
73+
1. 若两个堆都为空,那么插入 left 即可。
74+
2. 若right为空,left不为空:
75+
>= left[0] 若新数据大于 left[0] 表明应该插入到right里。
76+
< left[0] 新数据小于 left[0] 应该插入到left,这时要先将left的0弹出放到right里,在插入left。
77+
3. 都不为空时判断:
78+
长度相等时:
79+
> right[0] 表明不需要插入到 left 中,将 right[0] 弹出并插入left,然后插到 right 中即可。
80+
< right[0] 表明直接插入到 left 中即可。
81+
不等时:
82+
left多:
83+
不大于right[0]
84+
left 弹出,加入到 right。
85+
新加入的压入 left。
86+
大于:
87+
直接加入 right
88+
right多:
89+
不大于right[0]:
90+
直接加入 left.
91+
大于:
92+
right 弹出,加入到 left。
93+
新加入的加入到 right。
94+
95+
96+
测试数据的效率不等:
97+
100ms~500ms 都有跑过...
98+
99+
就巨大量的数据来说:
100+
使用堆应该是最好的选择,每一个操作都是 O(log n) 与 O(1) 级别的。
101+
102+
进阶条件的思考:
103+
1. 如果全部都是 0~100 之间的数据,完全可以建立一个哈希表,以数字为键,新加入的在它的数量上累积,寻找中位也简单。
104+
105+
测试地址:
106+
https://leetcode.com/problems/find-median-from-data-stream/description/
107+
108+
109+
"""
110+
# import bisect
111+
import heapq
112+
113+
class MedianFinder(object):
114+
115+
def __init__(self):
116+
"""
117+
initialize your data structure here.
118+
"""
119+
self.stream_data_left = []
120+
heapq.heapify(self.stream_data_left)
121+
self.stream_data_right = []
122+
heapq.heapify(self.stream_data_right)
123+
124+
125+
def addNum(self, num):
126+
"""
127+
:type num: int
128+
:rtype: void
129+
"""
130+
# heapq.heappush(self.stream_data_right, num)
131+
132+
# if len(self.stream_data_right) > len(self.stream_data_left):
133+
# pop = heapq.heappop(self.stream_data_right)
134+
# heapq.heappush(self.stream_data_left, -pop)
135+
if not self.stream_data_left:
136+
heapq.heappush(self.stream_data_left, -num)
137+
return
138+
139+
if not self.stream_data_right:
140+
if num > -self.stream_data_left[0]:
141+
heapq.heappush(self.stream_data_right, num)
142+
else:
143+
pop = heapq.heappop(self.stream_data_left)
144+
145+
heapq.heappush(self.stream_data_right, -pop)
146+
heapq.heappush(self.stream_data_left, -num)
147+
return
148+
149+
if len(self.stream_data_right) == len(self.stream_data_left):
150+
if num > self.stream_data_right[0]:
151+
heapq.heappush(self.stream_data_right, num)
152+
pop = heapq.heappop(self.stream_data_right)
153+
heapq.heappush(self.stream_data_left, -pop)
154+
155+
else:
156+
heapq.heappush(self.stream_data_left, -num)
157+
elif len(self.stream_data_left) > len(self.stream_data_right):
158+
if num > self.stream_data_right[0]:
159+
heapq.heappush(self.stream_data_right, num)
160+
else:
161+
heapq.heappush(self.stream_data_left, -num)
162+
pop = heapq.heappop(self.stream_data_left)
163+
164+
heapq.heappush(self.stream_data_right, -pop)
165+
else:
166+
if num < self.stream_data_right[0]:
167+
heapq.heappush(self.stream_data_left, -num)
168+
else:
169+
heapq.heappush(self.stream_data_right, num)
170+
pop = heap.heappop(self.stream_data_right)
171+
heapq.heappush(self.stream_data_left, -pop)
172+
173+
# bisect.insort_right(self.stream_data, num)
174+
175+
def findMedian(self):
176+
"""
177+
:rtype: float
178+
"""
179+
length = len(self.stream_data_left) + len(self.stream_data_right)
180+
if length % 2 == 0:
181+
return float((-self.stream_data_left[0] + self.stream_data_right[0])) / 2.0
182+
else:
183+
return -self.stream_data_left[0]
184+
185+
186+
# Your MedianFinder object will be instantiated and called as such:
187+
# obj = MedianFinder()
188+
# obj.addNum(num)
189+
# param_2 = obj.findMedian()

0 commit comments

Comments
 (0)