题目
给定一个非空的无需数组,输出该数组的中位数
例如:
1 | 输入:[8,13,7,21,15,20,30,9] |
思路一
首先我们想到的就是对数组进行排序,然后再求中位数,但是排序算法中最好快速排序时间复杂度为O(nlogn)
我们可以换一种思路,什么叫中位数?
中位数我们可以理解为它的左半部分比它小,它的右半部分比它大。说到这里,有没有感觉这种情况好像在哪实现过呢?没错,快速排序使用partition进行分区的时,最好的情况下会使数据分为两部分
所以我们可以将这道题升级为TOP N问题,通过调用快排的partition函数,找到中位数。
怎么利用partition函数呢?
1 | int partition(int[] arr, int start, int end); |
partition返回的是下标pivot,如果返回的下标pivot刚好等于中位数的下标,则直接返回值
如果pivot>midIdx, 往左边找,让end = pivot-1;
如果pivor<midIdx,往右边找,让end = pivot+1;
如果数组的个数是奇数,那么只要找到下标为 length/2 位置的pivot就是我们要找的中位数。如果数组的个数是偶数,那也简单,找到下标为 (length/2)-1 和 length/2
伪代码
1 | package algorithm; |
时间复杂度分析
partition的时间复杂度为O(n),如果一次没找到,理想情况下,下次从n/2个中寻找
所以O(n) = O(n) + O(n/2) + O(n/4) … +O(1)
所以时间复杂度为O(n),但是这只是理想情况,最差的时间复杂度有可能蜕变为O(n^2)
思路二
还有一种更好的解决方案,使用两个堆数据结构。我们通过维护一个大堆和一个小堆,小堆的元素都比大堆中的元素大
1、刚开始往大顶堆中插入数据
2、插入的数据如果比大顶堆大,则插入到小顶堆
3、插入的数据如果比大顶堆的堆顶元素小,则插入到大顶堆
4、大顶堆元素的个数要大于等于小顶堆的元素,但是不能比小顶堆多于1个,如果大顶堆中的元素个数比小顶堆多出1个以上,则弹出堆顶元素到小顶堆中
5、如果小顶堆的元素个数大于大顶堆,弹出小顶堆元素到大顶堆
如果数组元素个数为奇数,直接弹出大顶堆堆顶元素
如果数组元素个数为偶数,弹出大顶堆和小顶堆元素之和除以2
画个图开心一下
PS:原谅我第一次画图……
代码实现
1 | package algorithm; |
时间复杂度分析
遍历数据为O(n),调整堆的复杂度为O(log n/2)
所以时间复杂度为O(n*log n/2),n不是很大时,可以看成为O(n),稳定性比第一种方案好点