就借着月光,再与你对望
—— 24.10.14
295. 数据流的中位数
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。- 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。
void addNum(int num)
将数据流中的整数num
添加到数据结构中。
double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。示例 1:
输入 ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"] [[], [1], [2], [], [3], []] 输出 [null, null, null, 1.5, null, 2.0] 解释 MedianFinder medianFinder = new MedianFinder(); medianFinder.addNum(1); // arr = [1] medianFinder.addNum(2); // arr = [1, 2] medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2) medianFinder.addNum(3); // arr[1, 2, 3] medianFinder.findMedian(); // return 2.0
思路
由于寻找的是中位数,我们可以把所有的数据分成两份,由于是数据流的中位数,中间的数不断地变化,但是我们可以把数据流分成较大的数和较小的数两部分,找出较小数中最大的和较大数中最小的,用一个大顶堆和一个小顶堆实现,注意两个堆中元素个数最多相差一个,元素多的那个堆的堆顶元素就是我们查找的中位数,然后数据流中添加元素,在两个堆中继续进行判断,找到中位数
为了保证两边数据量的平衡
两边个数一样时,左边个数加一
两边个数不一样时,右边个数加一
但是,随便一个数能直接加入吗?
左边个数加一时,应该加入右边堆,跟右边堆中所有元素进行比较,挑右边堆中最小的元素取走加入到左边堆
右边个数加一时,应该加入左边堆,挑左边堆中所有元素进行比较,挑左边堆中最大的元素取走加入到右边堆
Heap类实现
import java.util.Arrays;
public class Heap {
int[] array;
int size;
boolean max;
public int size() {
return size;
}
public Heap(int capacity, boolean max) {
this.array = new int[capacity];
this.max = max;
}
public Heap(int[] array, boolean max) {
this.array = array;
this.size = array.length;
this.max = max;
heapify();
}
/**
* 获取堆顶元素
*
* @return 堆顶元素
*/
public int peek() {
return array[0];
}
/**
* 删除堆顶元素
*
* @return 堆顶元素
*/
public int poll() {
int top = array[0];
swap(0, size - 1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
*
* @param index 索引
* @return 被删除元素
*/
public int poll(int index) {
int deleted = array[index];
up(Integer.MAX_VALUE, index);
poll();
return deleted;
}
/**
* 替换堆顶元素
*
* @param replaced 新元素
*/
public void replace(int replaced) {
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
*
* @param offered 新元素
*/
public void offer(int offered) {
if (size == array.length) {
// 扩容
grow();
}
up(offered, size);
size++;
}
// 堆扩容
private void grow() {
// 长度增加为原先的1.5倍
int capacity = (int)(size * 1.5);
// int capacity = size + (size >> 1);
int[] newArray = new int[capacity];
System.arraycopy(array, 0,
newArray, 0, size);
array = newArray;
}
// 将 offered 元素上浮: 直至 offered 小于父元素或到堆顶
private void up(int offered, int index) {
int child = index;
while (child > 0) {
int parent = (child - 1) / 2;
boolean cmp = max ? offered > array[parent] : offered < array[parent];
if (cmp) {
array[child] = array[parent];
} else {
break;
}
child = parent;
}
array[child] = offered;
}
// 建堆
private void heapify() {
// 如何找到最后这个非叶子节点 size / 2 - 1
for (int i = size / 2 - 1; i >= 0; i--) {
down(i);
}
}
// 将 parent 索引处的元素下潜: 与两个孩子较大者交换, 直至没孩子或孩子没它大
private void down(int parent) {
int left = parent * 2 + 1;
int right = left + 1;
int maxOrMin = parent;
if (left < size && (max ? array[left] > array[maxOrMin] : array[left] < array[maxOrMin])) {
maxOrMin = left;
}
if (right < size && (max ? array[right] > array[maxOrMin] : array[right] < array[maxOrMin])) {
maxOrMin = right;
}
if (maxOrMin != parent) { // 找到了更大的孩子
swap(maxOrMin, parent);
down(maxOrMin);
}
}
// 交换两个索引处的元素
private void swap(int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
/*
100
/ \
10 99
/ \ / \
5 6 98 97
/\ /\ /
1 2 3 4 96
100
/ \
96 99
/ \ / \
10 6 98 97
/\ /\
1 2 3 4
*/
public static void main(String[] args) {
Heap heap = new Heap(5, true); //100,10,99,5,6,98,97,1,2,3,4,96
heap.offer(100);
heap.offer(10);
heap.offer(99);
heap.offer(5);
heap.offer(6);
heap.offer(98);
heap.offer(97);
heap.offer(1);
heap.offer(2);
heap.offer(3);
heap.offer(4);
heap.offer(96);
System.out.println(Arrays.toString(heap.array));
System.out.println(heap.size);
System.out.println(heap.poll(3));
System.out.println(Arrays.toString(heap.array));
System.out.println(heap.size);
}
}
求数据流中位数
import java.util.Arrays;
public class LeetCode295MiddleInDataStream {
@Override
public String toString() {
int size = left.size;
int[] left = new int[size];
for (int i = 0; i < left.length; i++) {
left[size - 1 - i] = this.left.array[i];
}
int[] right = Arrays.copyOf(this.right.array, this.right.size);
return Arrays.toString(left) + " <-> " + Arrays.toString(right);
}
public void addNum(int num) {
if (left.size() == right.size()){
right.offer(num);
left.offer(right.poll());
}else {
left.offer(num);
right.offer(left.poll());
}
}
private Heap left = new Heap(10,true);
private Heap right = new Heap(10,false);
// 两边数据一致,左右各取堆顶元素求平均左边多一个,取左边堆顶元素
public double findMedian(){
if (left.size() == right.size()){
return (left.peek() + right.peek()) / 2.0;
}else
return left.peek();
}
public static void main(String[] args) {
LeetCode295MiddleInDataStream obj = new LeetCode295MiddleInDataStream();
obj.addNum(10);
obj.addNum(5);
obj.addNum(3);
obj.addNum(4);
obj.addNum(6);
obj.addNum(7);
obj.addNum(8);
obj.addNum(9);
System.out.println(obj);
obj.addNum(10);
System.out.println(obj);
System.out.println(obj.findMedian());
}
}
力扣提交
import java.util.Arrays;
public class Heap {
int[] array;
int size;
boolean max;
public int size() {
return size;
}
public Heap(int capacity, boolean max) {
this.array = new int[capacity];
this.max = max;
}
/**
* 获取堆顶元素
*
* @return 堆顶元素
*/
public int peek() {
return array[0];
}
/**
* 删除堆顶元素
*
* @return 堆顶元素
*/
public int poll() {
int top = array[0];
swap(0, size - 1);
size--;
down(0);
return top;
}
/**
* 删除指定索引处元素
*
* @param index 索引
* @return 被删除元素
*/
public int poll(int index) {
int deleted = array[index];
up(Integer.MAX_VALUE, index);
poll();
return deleted;
}
/**
* 替换堆顶元素
*
* @param replaced 新元素
*/
public void replace(int replaced) {
array[0] = replaced;
down(0);
}
/**
* 堆的尾部添加元素
*
* @param offered 新元素
*/
public void offer(int offered) {
if (size == array.length) {
// 扩容
grow();
}
up(offered, size);
size++;
}
private void grow() {
int capacity = size + (size >> 1);
int[] newArray = new int[capacity];
System.arraycopy(array, 0,
newArray, 0, size);
array = newArray;
}
// 将 offered 元素上浮: 直至 offered 小于父元素或到堆顶
private void up(int offered, int index) {
int child = index;
while (child > 0) {
int parent = (child - 1) / 2;
boolean cmp = max ? offered > array[parent] : offered < array[parent];
if (cmp) {
array[child] = array[parent];
} else {
break;
}
child = parent;
}
array[child] = offered;
}
public Heap(int[] array, boolean max) {
this.array = array;
this.size = array.length;
this.max = max;
heapify();
}
// 建堆
private void heapify() {
// 如何找到最后这个非叶子节点 size / 2 - 1
for (int i = size / 2 - 1; i >= 0; i--) {
down(i);
}
}
// 将 parent 索引处的元素下潜: 与两个孩子较大者交换, 直至没孩子或孩子没它大
private void down(int parent) {
int left = parent * 2 + 1;
int right = left + 1;
int maxOrMin = parent;
if (left < size && (max ? array[left] > array[maxOrMin] : array[left] < array[maxOrMin])) {
maxOrMin = left;
}
if (right < size && (max ? array[right] > array[maxOrMin] : array[right] < array[maxOrMin])) {
maxOrMin = right;
}
if (maxOrMin != parent) { // 找到了更大的孩子
swap(maxOrMin, parent);
down(maxOrMin);
}
}
// 交换两个索引处的元素
private void swap(int i, int j) {
int t = array[i];
array[i] = array[j];
array[j] = t;
}
}
class MedianFinder {
Heap left = new Heap(10,true);
Heap right = new Heap(10,false);
public void addNum(int num) {
if (left.size == right.size){
right.offer(num);
left.offer(right.poll());
}else {
left.offer(num);
right.offer(left.poll());
}
}
public double findMedian() {
if (left.size() == right.size()){
return (left.peek() + right.peek()) / 2.0;
}else
return left.peek();
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/