インタビューの質問:
以下で編集 配列が与えられます。そこから 2 つのヒープを作成します。1 つは最小ヒープ、もう 1 つは最大ヒープです。これら 2 つの提供されたヒープを使用して、O(nlog n) 時間で配列の中央値を見つけます。
修正された質問 番号はランダムに生成され、(拡張) 配列に格納されます。中央値をどのように追跡しますか?
解決策 この問題は 2 つのヒープを使用して解決でき、中央値は常に O(1) 時間でアクセスできます。
インタビューの質問:
以下で編集 配列が与えられます。そこから 2 つのヒープを作成します。1 つは最小ヒープ、もう 1 つは最大ヒープです。これら 2 つの提供されたヒープを使用して、O(nlog n) 時間で配列の中央値を見つけます。
修正された質問 番号はランダムに生成され、(拡張) 配列に格納されます。中央値をどのように追跡しますか?
解決策 この問題は 2 つのヒープを使用して解決でき、中央値は常に O(1) 時間でアクセスできます。
両方のヒープを使用する方法は次のとおりです。要素の数がわからないことを前提としていることに注意してください。これが、最大ヒープからポップするもの以上の最小ヒープから何かをポップするまでポップする必要がある理由です。{1, 2, 3, 4}
中央値のようなセットの場合、実際には2.5
(2 つの「中間」値の平均) であるため、平均を返すことに注意してください。値型と仮定し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)
です。
2 つのヒープ、O(n log n) を使用する Java での実用的な実装。いつでも 2 つのヒープのサイズのバランスを保ちます (つまり、n%2==1 のように n 要素を入力した場合、それらの違いは最大で 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)
}
}
ヒープからのポップはO(log N)操作であるため、ヒープの1つから要素の半分をポップし、最後にポップされた値を取得することでO(N log N)を実現できます(エッジケースを処理する必要があります)。ただし、これは他のヒープを利用しません。
選択アルゴリズムを使用してO(N)を達成できますが、定数係数は非常に高くなります。すでにヒープがある場合は、前者の提案の方がおそらく良いでしょう。
2 つのヒープを使用する 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();
}
}