剑指Offer第十七天
大堆顶排序(中等)
题1:最小的k个数
输入整数数组
arr
,找出其中最小的k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
//用Arrays.sort()固然简单,但是这题的核心还是要手写快排吧。
if(k==0 || arr.length == 0){
return new int[0];
}
quickSort(arr, 0, arr.length-1);
return Arrays.copyOf(arr, k);
}
private void quickSort(int[] arr, int left, int right){
if(left >= right){
return ;
}
//定义两个指针
int i = left, j = right;
while(i < j){
//以基准数划分左右,左边为小于基准数的,右边为大于基准数的,让arr[left]作为基准数
while(i<j && arr[j] >= arr[left]){
j--;
}
while(i<j && arr[i] <= arr[left]){
i++;
}
swap(arr, i,j);
}
//将基准数放在中间,将一个数组划分成两个数组
swap(arr,i,left);
//递归
quickSort(arr, left, i-1);
quickSort(arr, i+1, right);
}
private void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
//快排就是,确定一个基准点,比如arr[left],然后从后面right开始比较,如果有小于这个基准点的,就把left位置的值替换成小于基准点的值,然后基准点备份原先left位置的值,然后在从前面找到一个值大于基准点,用于替换right位置的值,这样子一直换换换。。直到i==j
//这时候,相当于在i这个位置,把数组分成两部分,左边是小于基准点的,右边是大于基准点的
//所以分成两批就可以采用分治,进行处理(递归。。)
Tips:这题更好的做法是大根堆
// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断: // 1. 若目前堆的大小小于K,将当前数字放入堆中。 // 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过; // 反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。 class Solution { public int[] getLeastNumbers(int[] arr, int k) { if (k == 0 || arr.length == 0) { return new int[0]; } // 默认是小根堆,实现大根堆需要重写一下比较器。 Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1); for (int num: arr) { if (pq.size() < k) { pq.offer(num); } else if (num < pq.peek()) { pq.poll(); pq.offer(num); } } // 返回堆中的元素 int[] res = new int[pq.size()]; int idx = 0; for(int num: pq) { res[idx++] = num; } return res; } }
题2:数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) 从数据流中添加一个整数到数据结构中。
- double findMedian() 返回目前所有元素的中位数。
class MedianFinder {
//声明大堆顶
PriorityQueue<Integer> bigHeap;
//声明小堆顶
PriorityQueue<Integer> smallHeap;
public MedianFinder() {
//大堆顶初始化要重写比较器,因为队列默认实现是小堆顶
//大堆顶的堆顶元素是最大值,越往下越拉,类似普通班
bigHeap = new PriorityQueue<>((x,y)->(y-x));
//小堆顶的堆顶元素是最小值,越往下越猛,类似实验班
smallHeap = new PriorityQueue<>();
}
public void addNum(int num) {
//实验班加进来一个人,然后重新排序,把最拉的人放在堆顶
smallHeap.add(num);
//普通版接收实验班最拉的进来,然后重新排队,把最猛的放在堆顶
bigHeap.add(smallHeap.poll());
//如果实验班人太少,就把普通版最猛的抽调过来
if(smallHeap.size() + 1 < bigHeap.size()){
smallHeap.add(bigHeap.poll());
}
}
public double findMedian() {
//普通班人数大于等于实验班,如果是奇数情况
if(bigHeap.size()>smallHeap.size()){
return bigHeap.peek();
}
//如果是偶数情况
return (double)(smallHeap.peek() + bigHeap.peek())/2;
}
}