今早上在LintCode上做到了这种类型的题目,题目要求找到无序数组中位数在数组的位置,一开始想到的是利用快排的思想来做,但是由于只有十五分钟的时间,就直接用最普通的方式做了,思路是map记录位置+sort排序,水过去了。
找无序数组中位数我想着我之前逛知乎的时候遇到过,用最大堆和最小堆来做的。想了想,遇到这么多次,那就整理下方法吧。
这里中位数的定义是数组元素个数/2
1 直接排序找中位数
直接利用自带的sort方法排序,然后返回数组的中间索引的值
代码如下:
//1.直接排序
public static int findMediaMethod1(int[] a)
{
if(a.length==0) return -1;
Arrays.sort(a);
return a[(a.length-1)/2];
}
第一种方法太暴力了,我们只需要中位数即可,而不需要对数组中所有的元素进行处理。
这就引申出了第二种方法:
2 利用快排思想
快排的基本思想是选取一个哨兵元素,然后设立两个指针left,right,分别指向左端和右端,两个指针相向运动,当左右指针都遇到了违反数组次序的元素的时候左右指针交换元素(违反数组次序是指左指针找到了大于哨兵元素的元素或右指针找到了小于哨兵元素的值),这样当左右指针相遇的时候,左边的数组元素一定小于等于哨兵元素,右边的数组元素一定大于等于哨兵元素。我们可以利用这一性质来找中位数,每次进行快排操作后记录下哨兵元素的位置,如果大于中间元素的话,则我们只需要对左边进行快排操作,当小于中间元素,我们只需要对右边进行快排操作,跟二分查找很像。
代码如下:
//2.利用快排原理进行排序
public static int findMediaMethod2(int[] a)
{
if(a.length==0) return -1;
//中位数位置
int mid=(a.length-1)/2;
//左右指针位置
int left=0,right=a.length-1;
//进行快排操作后哨兵元素的位置
int qsIdx=0;
qsIdx = quickSort(left,right,a);
while(true)
{
//System.out.println("qsIdx= "+qsIdx);
if(qsIdx==mid)
{
break;
}
else if(qsIdx<mid) qsIdx=quickSort(qsIdx+1,right,a);
else qsIdx=quickSort(left,qsIdx-1,a);
}
return a[qsIdx];
}
public static int quickSort(int left,int right,int[] a)
{
int target=a[left];
while(left<right)
{
while(left<right&&a[right]>=target) right--;
a[left]=a[right];
while(left<right&&a[left]<=target) left++;
a[right]=a[left];
}
//System.out.println("left="+left);
a[left]=target;
return left;
}
3 利用大小堆性质
第三种方法我是看的牛客网上的一个回答才会的。这里附上链接:
点击这里
大顶堆存数组中较小部分的元素,小顶堆存数组中较大部分的元素,相当于将数组分成两半了,这样
大顶堆的堆顶或小顶堆的堆顶就是我们找的中位数。
如何实现呢?
1. 当插入堆中的元素个数为偶数时,将当前元素插入大顶堆,将大顶堆的堆顶元素插入小顶堆
2. 当插入堆中的元素个数为奇数时,将当前元素插入小顶堆,将小顶堆的堆顶元素插入大顶堆
hhh,是不是很神奇。
代码如下:
//3.利用大顶堆和小顶堆性质进行排序
public static int findMediaMethod3(int[] a)
{
if(a.length==0) return -1;
Queue<Integer> minHeap = new PriorityQueue<Integer>();
Queue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i=0;i<a.length;i++)
{
if(i%2==0)
{
maxHeap.add(a[i]);
int top = maxHeap.poll();
minHeap.add(top);
}
else
{
minHeap.add(a[i]);
int top = minHeap.poll();
maxHeap.add(top);
}
}
return a.length%2==0? maxHeap.peek():minHeap.peek();
}
总结
上面的三种方法,第一种肯定是不提倡的,第三种的话,因为一次操作就需要一次堆插入操作(log(m))(m表示堆中元素数目),n次操作的话就是nlog(m) ,第二种感觉的话应该是nlog(n)? 不过最官方的肯定是第三种方法。