中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
进阶:
如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-median-from-data-stream
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法:
1.每次添加数据时,维护元素的排序顺序。
使用插入排序的版本:
通过 | 572 ms | 93 MB |
class MedianFinder {
/** initialize your data structure here. */
private int [] list;
public int length;
public MedianFinder() {
list = new int[10000000];
length = 0;
}
public void addNum(int num) {
//插入排序
int i=0;
while(i < length && list[i] <= num){
i++;
}
//NT????????
for (int j = length+1; j > i; j--) {
list[j] = list[j-1];
}
list[i]=num;
length++;
}
public double findMedian() {
//
int k=0;
double res;
if(length%2==0){
k=length/2-1;
res = (list[k] + list[k+1])/2.0;
}else{
//奇数个
k=length/2+1-1;
res = list[k];
}
return res;
}
// public void print(){
// for (int i = 0; i < length; i++) {
// System.out.println(list[i]);
// }
// }
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
2.维护两个堆,一个最大堆,一个最小堆, 1)保持最小堆的元素大于最大堆。2)最大堆的元素个数和最小堆的个数差为1
最大堆放较小的数,最大堆放较大的数
如:1 2 3 4 5 最大堆放 3 1 2 最小堆放 4 5,那么中位数就是(4+3) /2 = 3.5
下面介绍两个思路流程,一个较复杂一个较为简单。
大小堆流程1(较复杂):
0.设大堆 堆顶元素为maxe,小堆 堆顶元素为mine,添加的元素为X
1.初始向大堆添加第一个元素(当大小堆的都为空时)
2.当大小堆元素数量相等时,如果元素X 大于maxe则将元素添加到小堆,
如果元素X 小于maxe则将元素添加到大堆。
3.当大小堆元素数量不等时
3.1如果大堆数量大于小堆:如果元素X 小于maxe则将大堆的堆顶移到小堆(有一个建堆调整过程),将X添加到大堆,否则将X添加到小堆
3.2如果大堆数量小于小堆:如果元素X 大于mine则将小堆的堆顶移到大堆(有一个建堆调整过程),将X添加到小堆,否则将X添加到大堆
大小堆思路流程2(较简洁):
1.如果大小堆元素数量相同时,将新数据添加到大堆,并将大堆顶数据移动到小堆。这样做的结果就是使得小堆的数量比大堆多一个,并保持大堆的元素始终小于等于小堆元素
2.如果大小堆数量不同(即小堆多一个),将新数据那添加到那小堆,将小堆堆顶数据移动到大堆。这样做使得大小堆数量相同,并保持小堆元素始终大于等于大堆
上三个版本的java代码:
版本1是自己实现的堆排序过程,版本2是使用java优先队列,这两个都是用的流程1,
第三个版本是使用 流程2
版本1.
通过 | 716 ms | 48.5 MB | Java |
class MedianFinder {
/** initialize your data structure here. */
private ArrayList<Integer> max_heap_list;
private ArrayList<Integer> min_heap_list;
public MedianFinder() {
max_heap_list = new ArrayList<>();
min_heap_list = new ArrayList<>();
max_heap_list.add(0);
min_heap_list.add(0);
}
public void adjust_down(ArrayList<Integer> list, int k, String mode){
//向下调整
list.set(0,list.get(k));
for(int i=k*2; i < list.size(); i=i*2){
if(i+1<list.size()){
if(mode.equals("max") && list.get(i)<list.get(i+1)){
i++;
}
if(mode.equals("min") && list.get(i)>list.get(i+1)){
i++;
}
//取子节点 较为mode 的那个
}
if(mode.equals("max") &&list.get(i)<list.get(0)){
//已经满足条件则退出
break;
}
if(mode.equals("min") &&list.get(i)>list.get(0)){
//已经满足条件则退出
break;
}
//将父节点位置替换为子节点较大值位置的值
list.set(k,list.get(i));
k=i;
}
list.set(k,list.get(0));
}
public void build_heap(ArrayList <Integer> list,String mode){
for(int i=list.size()/2;i>0;i--){
adjust_down(list,i,mode);
}
}
public int pop(ArrayList <Integer> list,String mode){
int res = list.get(1);
list.remove(1);
build_heap(list,mode);
return res;
}
public void adjust_up(ArrayList<Integer> list,String mode){
int k=list.size()-1;
int temp = list.get(k);
for(int i=k/2;i>0;i=i/2){
if(mode.equals("max")){
if(list.get(i)<temp){
list.set(k,list.get(i));
k=i;
}else{
break;
}
}
if(mode.equals("min")){
if(list.get(i)>temp){
list.set(k,list.get(i));
k=i;
}else{
break;
}
}
}
list.set(k,temp);
}
public void add_max(int num){
max_heap_list.add(num);
adjust_up(max_heap_list,"max");
}
public void add_min(int num){
min_heap_list.add(num);
adjust_up(min_heap_list,"min");
}
public void addNum(int num) {
//维护两个堆
if(max_heap_list.size()==1){
max_heap_list.add(num);
return;
}
// if(min_heap_list.size()==1){
// min_heap_list.add(num);
// return;
// }
// int max_head_elem = max_heap_list.get(1);
// int min_head_elem = min_heap_list.get(1);
if(max_heap_list.size()==min_heap_list.size()){
int max_head_elem = max_heap_list.get(1);
if(num<max_head_elem){
add_max(num);
}else{
add_min(num);
}
}else if(max_heap_list.size()>min_heap_list.size()){
int max_head_elem = max_heap_list.get(1);
if(num<max_head_elem){
pop(max_heap_list,"max");
add_min(max_head_elem);
add_max(num);
}else{
add_min(num);
}
}else{
int min_head_elem = min_heap_list.get(1);
if(num>min_head_elem){
pop(min_heap_list,"min");
add_max(min_head_elem);
add_min(num);
}else{
add_max(num);
}
}
}
public double findMedian() {
//
double res = 0;
if(max_heap_list.size()==min_heap_list.size()){
res = (max_heap_list.get(1)+min_heap_list.get(1))/2.0;
}else if(max_heap_list.size()>min_heap_list.size()){
res = max_heap_list.get(1);
}else{
res = min_heap_list.get(1);
}
return res;
}
public void print(){
System.out.println(max_heap_list.toString());
System.out.println(min_heap_list.toString());
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
版本2
通过 | 66 ms | 49.9 MB | Java |
class MedianFinder {
/** initialize your data structure here. */
private PriorityQueue<Integer> min_heap = new PriorityQueue<>();
private PriorityQueue<Integer> max_heap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
public MedianFinder() {
}
public void addNum(int num) {
if(max_heap.isEmpty()){
max_heap.add(num);
return;
}
if(min_heap.size()==max_heap.size()){
if(max_heap.peek()<num){
min_heap.add(num);
}else{
max_heap.add(num);
}
}else if(min_heap.size()<max_heap.size()){
if(max_heap.peek()>num){
int max_e = max_heap.poll();
max_heap.add(num);
min_heap.add(max_e);
}else{
min_heap.add(num);
}
}else {
if(num>min_heap.peek()){
int min_e = min_heap.poll();
min_heap.add(num);
max_heap.add(min_e);
}else{
max_heap.add(num);
}
}
}
public double findMedian() {
if(max_heap.size()==min_heap.size()){
return (max_heap.peek()+min_heap.peek())/2.0;
}else if (max_heap.size()>min_heap.size()){
return max_heap.peek();
}else {
return min_heap.peek();
}
}
void print(){
System.out.println(max_heap.toString());
System.out.println(min_heap.toString());
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
版本3
通过 | 65 ms | 49.7 MB | Java |
class MedianFinder {
/** initialize your data structure here. */
private PriorityQueue<Integer> min_heap = new PriorityQueue<>();
private PriorityQueue<Integer> max_heap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
public MedianFinder() {
}
public void addNum(int num) {
//思路:两个堆大小相同时,往大堆里添加,并将头移动到小堆,使得小堆比大堆多一个。
//不相同时(小堆比大堆多一个),往小堆添加新值,并将小堆的头移到大堆,保持平衡。
if(max_heap.size()==min_heap.size()){
max_heap.add(num);
min_heap.add(max_heap.poll());
}else{
min_heap.add(num);
max_heap.add(min_heap.poll());
}
}
public double findMedian() {
if(max_heap.size()==min_heap.size()){
return (max_heap.peek()+min_heap.peek())/2.0;
}else if (max_heap.size()>min_heap.size()){
return max_heap.peek();
}else {
return min_heap.peek();
}
}
void print(){
System.out.println(max_heap.toString());
System.out.println(min_heap.toString());
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
结语:
可以明显的发现思路二的方法比普通排序的方法耗时较少,除了我自己写的方法外(ps:可能自己写的实现效率不高吧。。)