Java 中流式运行整数的中位数

2024年9月10日 | 阅读 12 分钟

给定一个整数数组。计算输入数组中到目前为止遍历的元素的中间值。为简单起见,假设没有重复项。

示例

输入

int arr[] = {17, 11, 15, 13, 10, 12, 18, 19, 1, 16, 14, 20};

输出 {17, 14, 15, 14, 13, 12, 13, 14, 13, 14, 14, 14.5}

解释:我们从左到右开始读取数组。遍历的第一个元素是 17。因此,中位数是 17。因为只遍历了一个元素。之后,遍历了元素 11。现在,此时,我们已经遍历了两个元素 17 和 11。因此,中位数变为 (11 + 17) / 2 = 14。

之后,遍历了元素 15。现在我们总共有 3 个遍历过的元素,17、11 和 15,而奇数个元素的中间值是元素按升序排列时的中间元素。因此,这三个元素的中间值是 {11, 15, 17} => {15}。

现在,元素 13 进入了视野,总共遍历了 4 个元素,而偶数个元素的中间值是排序后两个中间元素的平均值。因此,当前中间值为:{11, 13, 15, 17} => (13 + 15) / 2 = 14。类似地,我们也可以计算其他中间值。

方法:使用插入排序

从上面的例子可以看出,为了计算中间值,需要对目前为止遍历过的整数进行排序。因此,我们需要一种排序技术来对元素进行排序。我们将使用插入排序来对元素进行排序。

文件名:RunningIntegerStream.java

输出

Median after reading element 17 is: 17
Median after reading { 11 17 } elements is 14.0
Median after reading { 11 15 17 } elements is 15.0
Median after reading { 11 13 15 17 } elements is 14.0
Median after reading { 10 11 13 15 17 } elements is 13.0
Median after reading { 10 11 12 13 15 17 } elements is 12.5
Median after reading { 10 11 12 13 15 17 18 } elements is 13.0
Median after reading { 10 11 12 13 15 17 18 19 } elements is 14.0
Median after reading { 1 10 11 12 13 15 17 18 19 } elements is 13.0
Median after reading { 1 10 11 12 13 15 16 17 18 19 } elements is 14.0
Median after reading { 1 10 11 12 13 14 15 16 17 18 19 } elements is 14.0
Median after reading { 1 10 11 12 13 14 15 16 17 18 19 20 } elements is 14.5

复杂度分析:程序使用了插入排序技术。因此,程序的 time complexity 为 O(n2)。程序不使用任何数据结构。因此,程序 space complexity 为常数,即 O(1)。

注意:有人可能会争论为什么程序中使用了插入排序技术。也可以使用其他排序技术,例如选择排序、归并排序等。这样做的原因是,在插入排序的任何给定实例中,例如对第 k 个元素进行排序,输入数组的前 k 个元素已排序。插入排序不依赖于即将到来的数据(未来数据)来对当前的数据进行排序。换句话说,插入排序在插入下一个元素时对到目前为止的数据进行排序。这是插入排序的一个重要方面,使得该算法成为一个在线算法

方法:使用 AVL 树

使用 AVL 树,可以找到流式整数的中位数。我们将一个接一个地将元素插入 AVL 树,并跟踪

文件名:RunningIntegerStream1.java

输出

Median after reading element 17 is: 17
Median after reading { 17 11 } elements is 14.0
Median after reading { 17 11 15 } elements is 15.0
Median after reading { 17 11 15 13 } elements is 14.0
Median after reading { 17 11 15 13 10 } elements is 13.0
Median after reading { 17 11 15 13 10 12 } elements is 12.5
Median after reading { 17 11 15 13 10 12 18 } elements is 13.0
Median after reading { 17 11 15 13 10 12 18 19 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 1 } elements is 13.0
Median after reading { 17 11 15 13 10 12 18 19 1 16 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 1 16 14 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 1 16 14 20 } elements is 14.5

复杂度分析:在 AVL 树中,插入元素的 time complexity 为 O(log(n))。因此,程序的 overall time complexity 为 O(n x log(n))。程序为 AVL 树的节点分配内存。因此,space complexity 为 O(n),其中 n 是已遍历的流式整数的总数。

方法:使用最小堆和最大堆

概念是使用最小堆和最大堆来保存下半部分和上半部分元素的。Java 中的最小堆和最大堆可以通过 PriorityQueue 来实现。解决问题需要以下步骤。

算法

步骤 1:创建两个堆:一个用于最大堆,用于在任何给定时间维护下半部分的元素;另一个用于最小堆,用于维护上半部分的元素。

步骤 2:将中位数的值初始化为 0。

步骤 3:对于每个当前读取的元素,将其插入最小堆或最大堆,并根据以下条件计算中位数

  • 如果最大堆的大小大于最小堆的大小,并且该元素不大于前一个中位数,则从最大堆的顶部移除该元素并将其放入最小堆,然后将新元素放入最大堆。否则,将新元素放入最小堆。将当前中位数计算为最小堆和最大堆顶部元素的总和除以 2。
  • 如果最大堆的大小小于最小堆的大小,并且该元素大于前一个中位数,则从最小堆的顶部移除该元素并将其放入最大堆,然后将新元素放入最小堆。否则,将新元素放入最大堆。将当前中位数计算为最小堆和最大堆顶部元素的总和除以 2。
  • 如果两个堆的大小相同,则检查当前元素是否小于前一个中位数。如果是,则将当前元素插入最大堆,最大堆顶部的元素就是我们当前的中位数。如果不是,则将当前元素插入最小堆,最小堆顶部的元素就是我们当前的中位数。

文件名:DisplayLeafBST1.java

输出

Median after reading element 17 is: 17
Median after reading { 17 } elements is 14.0
Median after reading { 17 11 } elements is 15.0
Median after reading { 17 11 15 } elements is 14.0
Median after reading { 17 11 15 13 } elements is 13.0
Median after reading { 17 11 15 13 10 } elements is 12.5
Median after reading { 17 11 15 13 10 12 } elements is 13.0
Median after reading { 17 11 15 13 10 12 18 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 } elements is 13.0
Median after reading { 17 11 15 13 10 12 18 19 1 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 1 16 } elements is 14.0
Median after reading { 17 11 15 13 10 12 18 19 1 16 14 } elements is 14.5

复杂度分析:程序的 time complexity 和 space complexity 与上一个程序相同。