【剑指Offer】数据流中的中位数——四种解法

数据流中的中位数——四种解法

题目描述:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

解题思路:

方法1:使用无序数组存储,然后需要对数组进行排序,然后就可以顺利找到中位数。
方法2:在插入数组时,就进行排序,形成有序的数组,排完序后就可以直接找到中位数。
方法3:使用树结构,二叉搜索树或者平衡二叉树,都是在插入的时候完成排序。
方法4:使用两个堆:最大堆和最小堆。

完整代码:

方法1:使用无序数组,直接调用库进行排序

import java.util.*;

public class MedianOfStream_JZ {
    /**
     * 数据流中的中位数
     *
     * @param num
     */
    // 方法1
    ArrayList<Integer> array = new ArrayList<>();

    public void Insert(Integer num) {
        array.add(num);
    }

    public Double GetMedian() {
        Collections.sort(array);
        int index = array.size() / 2;
        if (array.size() % 2 != 0) {// 奇数直接取
            return (double) array.get(index);
        }
        return (array.get(index - 1) + array.get(index)) / 2.0;
    }
}

方法2:插入的时候排序,即利用堆排序,每次加入一个元素就调整堆

    // 方法2
    private List<Integer> list = new LinkedList<>();

    public void Insert2(Integer num) {
        if (list.size() == 0) {
            list.add(num);
            return;
        }
        int first = 0;
        int last = list.size() - 1;
        int mid = 0;
        while (first < last) {
            mid = first + (last - first) / 2;
            if (list.get(mid) > num) {
                last = mid - 1;
            } else {
                first = mid + 1;
            }
        }
        list.add(first, num);
        return;
    }

    public Double GetMedian2() {
        int index = list.size();
        if (index % 2 == 1) {
            return (double) list.get(index / 2);
        }
        return ((double) list.get(index / 2 - 1) + (double) list.get(index / 2)) / 2;
    }

方法3:使用树,通过有序树获取中位数。注意,在Set集合中,没有get方法,所以无法直接获取某个下标所对应的元素,需要将Set转换位List。

import java.util.*;

public class Solution {
    private TreeSet<Integer> tree = new TreeSet<>();
    public void Insert(Integer num) {
        tree.add(num);
    }
    public Double GetMedian() {
        ArrayList<Integer> list = new ArrayList<>();
        list.addAll(tree);
        int index = list.size();
        if (index % 2 == 1) {
            return (double) list.get(index / 2);
        }
        return ((double) list.get(index / 2 - 1) + (double) list.get(index / 2)) / 2;
    }
}

方法4:使用两个堆。
具体过程为:
将读入的数据分为几乎数量相同的两部分,一部分数字小,另一部分大。小的一部分采用大顶堆存放,大的一部分采用小顶堆存放。这样两个堆的堆顶就是整个数据流中,最中间的两个数。当总个数为偶数时,使两个堆的数目相同,则中位数等于大顶堆的最大数字与小顶堆的最小数字的平均值;而总个数为奇数时,使小顶堆的个数比大顶堆多1,则中位数等于小顶堆的最小数字。
插入步骤为:
1. 如果已读取的个数为偶数(包括0)时,两个堆的数目已经相同,再插入一个数时,应该选一个数插入到小顶堆中,从而实现小顶堆的个数多1。但是,不能直接插入到小顶堆,本应该选择一个数加入到小顶堆中,但是必须选一个较大的数放入小顶堆,而插入的这个数不一定符合要求,所以这个数要和大顶堆的最大数比较,哪个较大,就把哪个放进小顶堆。
2. 如果已读取的个数为奇数,小顶堆的个数多1,则要将某个数插入到大顶堆中,所以新进来的数要和小顶堆的堆顶元素比较,更小的那个元素进入大顶堆。
这里使用PriorityQueue实现,默认情况下,PriorityQueue是一个小顶堆,因此需要自己定义比较器实现大顶堆.

import java.util.*;

public class Solution {
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    int count = 0;
    public void Insert(Integer num) {
        // 个数为偶数,则先插入大顶堆,并调整,然后将大顶堆中的最大数插入到小顶堆中
        if (count % 2 == 0) {
            maxHeap.offer(num);
            int max = maxHeap.poll();
            minHeap.offer(max);
        } else {
            //个数为奇数时,则先插入小顶堆,然后将小顶堆中最小的数插入到大顶堆中
            minHeap.offer(num);
            int min = minHeap.poll();
            maxHeap.offer(min);
        }
        count++;
    }
    public Double GetMedian() {
        // 当前为偶数个,则取大顶堆和小顶堆的堆顶元素的平均值
        if (count % 2 == 0) {
            return (minHeap.peek() + maxHeap.peek()) / 2.0;
        } else {
            // 当前为奇数个,则直接从小顶堆中取元素即可,所以保证小顶堆中元素的个数
            return minHeap.peek() * 1.0;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值