跟踪扩展阵列的中位数

时间:2011-02-02 17:20:56

标签: algorithm heap complexity-theory

面试问题:

编辑在下方 你得到一个数组。你从它做出2个堆,一个minheap和另一个最大堆。现在在O(nlog n)时间内使用这2个提供的堆找到数组的中值。

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

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

4 个答案:

答案 0 :(得分:13)

以下是您如何使用这两种堆。请注意,我假设您不知道元素的数量,这就是为什么我们必须弹出,直到我们从最小堆中弹出大于或等于我们从最大堆弹出的内容。请注意,我们返回平均值,因为在类似{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)

答案 1 :(得分:4)

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)
    }
}

答案 2 :(得分:3)

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

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

答案 3 :(得分:-1)

使用两个堆的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();
  }
}