剑指offer第二版-41.数据流中的中位数

本系列导航:剑指offer(第二版)java实现导航帖

面试题41:数据流中的中位数

题目要求:
得到一个数据流中的中位数。

解题思路:
此题的关键在于“数据流”,即数字不是一次性给出,解题的关键在于重新写一个结构记录历史数据。下面给出三种思路,分别借助于链表、堆、二叉搜索树完成。

思路1:使用未排序的链表存储数据,使用快排的分区函数求中位数;也可以在插入时进行排序,这样中位数的获取会很容易,但插入费时。

思路2:使用堆存储,两个堆能够完成最大堆-最小堆这样的简单分区,两个堆的数字个数相同或最大堆数字个数比最小堆数字个数大1,中位数为两个堆堆顶的均值或者最大堆的堆顶。

思路3:使用二叉搜索树存储,每个树节点添加一个表征子树节点数目的字段用于找到中位数。

package chapter5;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Author: ryder
 * Date  : 2017/8/1
 * Time  : 11:06
 * Description:数据流中的中位数
 **/
public class P214_StreamMedian {
    public static interface MedianFinder{
        //模拟读取数据流的过程
        void addNum(double num);
        double findMedian();
    }
    //使用未排序的链表存储数据,使用快排的分区函数求中位数;
    //也可以在插入时进行排序,这样中位数的获取会很容易,但插入费时。
    public static class MedianFinder1 implements MedianFinder{
        List<Double> list = null;
        public MedianFinder1() {
            list = new LinkedList<>();
        }
        @Override
        public void addNum(double num) {
            list.add(num);
        }
        @Override
        public double findMedian() {
            if(list.size()==0)
                return 0;
            //如果长度为奇数,求中间的那个数;如果为偶数,求中间两个数的均值
            if((list.size()&1)==1)
                return findKth(list.size()>>>1);
            else
                return (findKth(list.size()>>>1)+findKth((list.size()>>>1)-1))/2;
        }
        //得到从小到大排序后下标为k的数据值
        private double findKth(int k){
            int start=0,end=list.size()-1;
            int index = partition(start,end);
            while (index!=k){
                if(index<k)
                    index = partition(index+1,end);
                else
                    index = partition(start,index-1);
            }
            return list.get(index);
        }
        //分区,[小,pivot,大]
        private int partition(int start,int end){
            if(start>=end)
                return end;
            double pivot = list.get(start);
            //[start,bound)小于等于pivot,bound值为pivot,(bound,end]大于pivot
            int bound = start;
            for(int i=start+1;i<=end;i++){
                if(list.get(i)<=pivot){
                    list.set(bound,list.get(i));
                    bound++;
                    list.set(i,list.get(bound));
                }
            }
            list.set(bound,pivot);
            return bound;
        }
    }

    //思路二:使用堆存储
    //两个堆能够完成最大堆-最小堆这样的简单分区,两个堆的数字个数相同或heapMax比heapMin大1
    //中位数为最大堆的堆顶或者两个堆堆顶的均值
    public static class MedianFinder2 implements MedianFinder{
        List<Double> maxHeap = null;
        List<Double> minHeap = null;
        public MedianFinder2() {
            maxHeap = new ArrayList<>();
            minHeap = new ArrayList<>();
            maxHeap.add(0.0);
            minHeap.add(0.0);
        }
        @Override
        public void addNum(double num) {
            if(maxHeap.size()==minHeap.size()){
                if(minHeap.size()<=1||num<=minHeap.get(1))
                    addItemToMaxHeap(num);
                else{
                    addItemToMaxHeap(minHeap.get(1));
                    minHeap.set(1,num);
                    adjustMinHeap(1);
                }
            }
            else{
                if(num>=maxHeap.get(1))
                    addItemToMinHeap(num);
                else{
                    addItemToMinHeap(maxHeap.get(1));
                    maxHeap.set(1,num);
                    adjustMaxHeap(1);
                }

            }
        }
        private void addItemToMaxHeap(double value){
            maxHeap.add(value);
            int curIndex = maxHeap.size()-1;
            while (curIndex>1 && maxHeap.get(curIndex)>maxHeap.get(curIndex>>>1)){
                double temp = maxHeap.get(curIndex);
                maxHeap.set(curIndex,maxHeap.get(curIndex>>>1));
                maxHeap.set(curIndex>>>1,temp);
                curIndex = curIndex>>>1;
            }
        }
        private void adjustMaxHeap(int index){
            int left = index*2,right=left+1;
            int max = index;
            if(left<maxHeap.size()&&maxHeap.get(left)>maxHeap.get(max))
                max = left;
            if(right<maxHeap.size()&&maxHeap.get(right)>maxHeap.get(max))
                max = right;
            if(max!=index){
                double temp = maxHeap.get(index);
                maxHeap.set(index,maxHeap.get(max));
                maxHeap.set(max,temp);
                adjustMaxHeap(max);
            }
        }
        private void addItemToMinHeap(double value){
            minHeap.add(value);
            int curIndex = maxHeap.size()-1;
            while (curIndex>1 && minHeap.get(curIndex)<minHeap.get(curIndex>>>1)){
                double temp = minHeap.get(curIndex);
                minHeap.set(curIndex,minHeap.get(curIndex>>>1));
                minHeap.set(curIndex>>>1,temp);
                curIndex = curIndex>>>1;
            }
        }
        private void adjustMinHeap(int index){
            int left = index*2,right=left+1;
            int min = index;
            if(left<minHeap.size()&&minHeap.get(left)<minHeap.get(min))
                min = left;
            if(right<minHeap.size()&&minHeap.get(right)<minHeap.get(min))
                min = right;
            if(min!=index){
                double temp = minHeap.get(index);
                minHeap.set(index,minHeap.get(min));
                minHeap.set(min,temp);
                adjustMinHeap(min);
            }
        }
        @Override
        public double findMedian() {
            if(maxHeap.size()>minHeap.size())
                return maxHeap.get(1);
            else
                return (maxHeap.get(1)+minHeap.get(1))/2;
        }
    }

    //思路三:使用二叉搜索树存储,每个树节点添加一个表征子树节点数目的字段用于计算中位数。
    public static class TreeNodeWithNums<T> {
        public T val;
        public int nodes;
        public TreeNodeWithNums<T> left;
        public TreeNodeWithNums<T> right;
        public TreeNodeWithNums(T val){
            this.val = val;
            this.nodes = 1;
            this.left = null;
            this.right = null;
        }
    }
    public static class MedianFinder3 implements MedianFinder {
        //左孩子小于根节点,右孩子大于等于根节点
        TreeNodeWithNums<Double> root = null;
        public MedianFinder3() {
        }
        @Override
        public void addNum(double num) {
            if(root==null)
                root = new TreeNodeWithNums<>(num);
            else
                addNode(root,num);
        }
        public void addNode(TreeNodeWithNums<Double> node, double num){
            if(num<node.val){
                if(node.left==null){
                    node.left = new TreeNodeWithNums<>(num);
                    node.nodes++;
                }
                else
                    addNode(node.left,num);
            }
            else{
                if(node.right==null){
                    node.right = new TreeNodeWithNums<>(num);
                    node.nodes++;
                }
                else
                    addNode(node.right,num);
            }
        }
        @Override
        public double findMedian() {
            if(root==null)
                return 0;
            if((root.nodes&1)==1)
                return findMedian(root,root.nodes/2+1);
            else
                return (findMedian(root,root.nodes/2)+findMedian(root,root.nodes/2+1))/2;
        }
        private double findMedian(TreeNodeWithNums<Double> node,int index){
            if(node.left!=null){
                if(index==node.left.nodes+1)
                    return node.val;
                else if(index<=node.left.nodes)
                    return findMedian(node.left,index);
                else
                    return findMedian(node.right,index-node.left.nodes-1);
            }
            else if(node.right!=null){
                return findMedian(node.right,index-1);
            }
            else
                return node.val;
        }
    }
    public static void main(String[] args){
        MedianFinder medianFinder1 = new MedianFinder1();
        medianFinder1.addNum(2);
        medianFinder1.addNum(1);
        System.out.println(medianFinder1.findMedian());
        MedianFinder medianFinder2 = new MedianFinder2();
        medianFinder2.addNum(2);
        medianFinder2.addNum(1);
        System.out.println(medianFinder2.findMedian());
        MedianFinder medianFinder3 = new MedianFinder3();
        medianFinder3.addNum(2);
        medianFinder3.addNum(1);
        System.out.println(medianFinder3.findMedian());

        medianFinder1.addNum(4);
        medianFinder2.addNum(4);
        medianFinder3.addNum(4);
        System.out.println(medianFinder1.findMedian());
        System.out.println(medianFinder2.findMedian());
        System.out.println(medianFinder3.findMedian());

    }
}

运行结果

1.5
1.5
1.5
2.0
2.0
2.0
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容