【问题标题】:Keeping track of the median of an expanding array跟踪扩展数组的中位数
【发布时间】:2011-02-02 17:20:56
【问题描述】:

面试题:

在下方编辑 给你一个数组。你用它做了 2 个堆,一个 minheap 和另一个 max heap。现在使用这 2 个提供的堆在 O(nlog n) 时间内找到数组的中位数。

更正的问题 数字是随机生成并存储到(扩展)数组中的。您将如何跟踪中位数?

解决方案 这个问题可以使用 2 个堆来解决,并且总是可以在 O(1) 时间内访问中位数。

【问题讨论】:

  • 我猜实际的问题是即使在多次插入之后也能够快速确定中位数并且不知何故在翻译中迷路了。坦率地说,这看起来像是一个伪装成面试问题的作业问题。
  • @Moron:不同意家庭作业评估。只复制作业陈述而不引入“翻译中丢失”问题太容易了。这听起来更像是一个在翻译中丢失的面试问题。
  • @Jason:我并不是说 EFreak 已经这样做了(或者没有这样做)。可能面试官可能已经看过教科书......
  • 如果允许你花费 O(N log N) 时间,为什么不直接对数组进行排序呢?
  • 伙计们!我想我把这个问题搞砸了:P n @Moron:我不上学:)所以没有作业问题。我现在发布更正后的问题。

标签: algorithm heap complexity-theory


【解决方案1】:

以下是您使用两个堆的方法。请注意,我假设您不知道元素的数量,这就是为什么我们必须弹出直到我们从最小堆中弹出大于或等于从最大堆中弹出的东西的原因。请注意,我们返回平均值是因为对于像 {1, 2, 3, 4} 这样的集合,中位数实际上是 2.5(两个“中间”值的平均值)。我假设 double 作为值类型,但这显然可以是任何东西。这里:

double min = minheap.pop();
double max = maxheap.pop();
while(min < max) {
    min = minheap.pop();
    max = maxheap.pop();
}

return (min + max) / 2;

因为弹出是O(log n),我们必须弹出O(n / 2) 值,所以这是O(n log n)

【讨论】:

  • @marcog:你有没有想过这个问题?例如,考虑{1, 2, 3}。 Pop 将min 作为1max 作为3。 Pop 再次将min 作为2max 作为2。条件min &lt; max 失败,所以我们返回(min + max) / 2 = 2。我挑战你提供一个这个算法失败的例子。
  • @Jason 考虑 1、2、2。Pop 给出 min 1 max 2。Pop 再次给出 min 2 max 1。然后当答案应该是 2 时返回 1.5。(编辑:我错了,第二个 pop 给出 min 2 max 2。)
  • @Jason 我失败了,抱歉我才意识到我的错误。反转投票。道歉。
  • @marcog:不,你不理解算法。 Pop 将min 作为1max 作为2。条件min &lt; max 没问题,所以继续下一次迭代。 Pop 给min2max2(是的,这就是堆的工作方式)。条件min &lt; max 失败,因此返回(min + max) / 2 = 2
  • 是的,所以当你不知道 N 时这是合适的,但如果你得到一个数组开始,那么显然你知道。即使这样,两个堆仍然是大材小用——您可以通过查找树的深度 O(log N) 来找到 N,或者,如果堆存储为数组,则 N 是数组的长度。我知道这并没有回答最初的问题,所以我想我很好奇是否存在使用两个堆是唯一或最佳解决方案的情况?
【解决方案2】:

Java 中的一个工作实现,使用 2 个堆,O(n log n)。在任何时候,我都会保持两个堆的大小平衡(即,如果我们输入了 n 个元素,使得 n%2==1,它们最多相差 1)。获得中位数是 O(1)。添加一个新元素是 O(log n)。

public class MedianOfStream {

    private int count;
    private PriorityQueue<Integer> highs, lows;

    public MedianOfStream() {
        highs = new PriorityQueue<Integer>(11, new Comparator<Integer>() {
            @Override
            public int compare(Integer arg0, Integer arg1) {
                return arg0.compareTo(arg1);
            }
        });
        lows = new PriorityQueue<Integer>(11, new Comparator<Integer>() {
            @Override
            public int compare(Integer arg0, Integer arg1) {
                return arg1.compareTo(arg0);
            }
        });
    }

    private int getMedian() {
        if (count == 0)
            return 0;
        if (lows.size() == highs.size()) {
            return (lows.peek() + highs.peek()) / 2;
        } else if (lows.size() < highs.size()) {
            return highs.peek();
        }
        return lows.peek();
    }

    private void swap(){
        int h = highs.poll();
        int l = lows.poll();
        highs.add(l);
        lows.add(h);
    }

    public int updateMedian(int n) {
        count++;

        if (count == 1)
            lows.add(n);

        else if (count==2) {
            highs.add(n);
            if(highs.peek()<lows.peek()) {
                swap(); // O(log n)
            }
        }

        else {
            if (n > highs.peek()) {
                lows.add(highs.poll()); // O(log n)
                highs.add(n); // O(log n)
            } else {
                highs.add(lows.poll()); // O(log n)
                lows.add(n); // O(log n)
            }
            if(highs.peek()<lows.peek()) {
                swap(); // O(log n)
            }
        }

        // if we added an even # of items,
        // the heaps must be exactly the same size,
        // otherwise we tolerate a 1-off difference

        if (Math.abs(lows.size() - highs.size()) > (count % 2)) {
            if (lows.size() < highs.size()) {
                lows.add(highs.poll()); // O(log n)
            } else {
                highs.add(lows.poll()); // O(log n)
            }
        }

        return getMedian(); // O(1)
    }
}

【讨论】:

    【解决方案3】:

    从堆中弹出是一个 O(log N) 操作,因此您可以通过从其中一个堆中弹出一半元素并获取最后弹出的值来实现 O(N log N)(您必须处理边缘例)。不过,这并没有利用其他堆。

    使用selection algorithm 可以实现 O(N),但常数因子非常高。如果您已经有一个堆,前一个建议可能会更好。

    【讨论】:

    • 是的 .. 问题是 O(nlog n) 本身.. 发布不正确。
    • @EFreak 这么想,答案已更新。 @Downvoter 请解释一下
    • @marcog:我会按照面试官告诉我的去做。在这里,根据@EFreak,在 O(nlog n) 时间内使用这 2 个提供的堆找到数组的中位数。面试问题有很多要点。有一点通常是为了测试您的特定知识,通常是作为查看您确实拥有多少知识的一种方式。所以在这里,例如,让我们看看受访者是否知道堆。这将使我对他们是否了解基本的数据结构和算法有所了解。第二点是看你是否是一个团队合作者,或者是否能听从指示,等等。
    • @Jason "你得到了一个数组"
    • @Jason:2 堆的使用。如果您允许将来插入,则可以在 O(1) 中找到中位数(插入为 O(log n))。
    【解决方案4】:

    使用两个堆的 JavaScript 解决方案:

    function addNewNumber(minHeap, maxHeap, randomNumber) {
      if (maxHeap.size() === minHeap.size()) {
        if (minHeap.peek() && randomNumber > minHeap.peek()) {
          maxHeap.insert(minHeap.remove());
          minHeap.insert(randomNumber);
        } else {
          maxHeap.insert(randomNumber);
        }
      } else {
        if (randomNumber < maxHeap.peek()) {
          minHeap.insert(maxHeap.remove());
          maxHeap.insert(randomNumber);
        } else {
          minHeap.insert(randomNumber);
        }
      }
    }
    
    function getMedian(minHeap, maxHeap) {
      if (!maxHeap.size()) {
        return 0;
      }
      if (minHeap.size() === maxHeap.size()) {
        return (minHeap.peek() + maxHeap.peek()) / 2;
      } else {
        return maxHeap.peek();
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-07
      • 2015-02-20
      • 1970-01-01
      相关资源
      最近更新 更多