私はFacebookとのインタビューをしました、そして彼らは私にこの質問をしました。
N個の異なる値を持つ順序付けされていない配列があるとします
$ input = [3,6,2,8,9,4,5]
K番目に大きい値を見つける関数を実装します。
EG:K = 0の場合は9を返します。K= 1の場合は8を返します。
私がしたことはこの方法でした。
private static int getMax(Integer[] input, int k)
{
List<Integer> list = Arrays.asList(input);
Set<Integer> set = new TreeSet<Integer>(list);
list = new ArrayList<Integer>(set);
int value = (list.size() - 1) - k;
return list.get(value);
}
私はテストしたところ、その方法は質問に基づいてうまく機能しました。ただし、インタビュイーはin order to make your life complex! lets assume that your array contains millions of numbers then your listing becomes too slow. What you do in this case?
をヒントに、min heap
の使用を提案しました。私の知識に基づいて、ヒープの各子の値はルートの値を超えてはなりません。したがって、この場合、3をルートとすると、6はその子であり、その値はルートの値よりも大きくなります。私はおそらく間違っていますが、あなたはどう思いますか、min heap
に基づいたその実装は何ですか?
彼は実際にあなたにすべての答えを与えました。単なるヒントではありません。
そして、あなたの理解はmax heap
に基づいています。 min heap
ではありません。そして、その働きは一目瞭然です。
最小ヒープでは、ルートの値は最小(子より少ない)です。
したがって、必要なのは、配列を反復処理してK
要素をmin heapに入力することです。これが完了すると、ヒープにはルートの最下位が自動的に含まれます。
次に、配列から読み取った(next)要素ごとに、->値が最小ヒープのルートより大きいかどうかを確認します。 ->はいの場合、最小ヒープからルートを削除し、それに値を追加します。
配列全体をトラバースした後、min heapのルートには、k
th番目に大きい要素が自動的に含まれます。
そして、ヒープ内の他のすべての要素(正確にはk-1要素)はk
より大きくなります。
JavaでのPriorityQueueを使用したMin Heapの実装を次に示します。 複雑さ:_n * log k
_。
_import Java.util.PriorityQueue;
public class LargestK {
private static Integer largestK(Integer array[], int k) {
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(k+1);
int i = 0;
while (i<=k) {
queue.add(array[i]);
i++;
}
for (; i<array.length; i++) {
Integer value = queue.peek();
if (array[i] > value) {
queue.poll();
queue.add(array[i]);
}
}
return queue.peek();
}
public static void main(String[] args) {
Integer array[] = new Integer[] {3,6,2,8,9,4,5};
System.out.println(largestK(array, 3));
}
}
_
出力:5
コードは、配列O(n)
をループします。 PriorityQueue(最小ヒープ)のサイズはkなので、どの操作も_log k
_になります。すべての数値がソートされる最悪のシナリオ[〜#〜] asc [〜#〜]の場合、複雑さは_n*log k
_です。ヒープの上部を削除して新しい要素を挿入する必要がある要素。
Edit:これをチェック answer for O(n)ソリューション。
この問題を解決するには、おそらく PriorityQueue も使用できます。
_public int findKthLargest(int[] nums, int k) {
int p = 0;
int numElements = nums.length;
// create priority queue where all the elements of nums will be stored
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
// place all the elements of the array to this priority queue
for (int n : nums){
pq.add(n);
}
// extract the kth largest element
while (numElements-k+1 > 0){
p = pq.poll();
k++;
}
return p;
}
_
Javaから doc :
実装メモ:この実装は、エンキューおよびデキューのメソッド(
offer
、poll
、remove()
にO(log(n))時間を提供しますおよびadd
);remove(Object)
およびcontains(Object)
メソッドの線形時間。検索メソッド(peek
、element
、およびsize
)の一定時間。
Forループはn
回実行され、上記のアルゴリズムの複雑さはO(nlogn)
です。
配列/ストリームの要素数が不明な場合は、ヒープベースのソリューションが最適です。しかし、それらが有限であるにもかかわらず、線形時間で最適化されたソリューションが必要な場合はどうでしょうか。
ここ で説明したクイックセレクトを使用できます。
配列= [3,6,2,8,9,4,5]
最初の要素としてピボットを選択しましょう:
ピボット= 3(0番目のインデックス)、
以下のすべての要素が左側にあり、3より大きい数が右側になるように配列を分割します。クイックソートで行われるように(私の blog で議論されています)。
したがって、最初のパスの後-[2、3、6,8,9,4,5]
ピボットインデックスは1です(つまり、2番目に低い要素です)。同じプロセスをもう一度適用します。
今選択した6、前のピボットの後のインデックスの値-[2,3,4,5、6、8,9]
したがって、6は適切な場所にあります。
適切な数(各反復で最大k番目または最小k番目)を見つけたかどうかを確認し続けます。見つかった場合は、それ以外の場合は続行します。
k
の定数値の1つのアプローチは、部分挿入ソートを使用することです。
(これは別個の値を想定していますが、重複を処理するように簡単に変更できます)
_last_min = -inf
output = []
for i in (0..k)
min = +inf
for value in input_array
if value < min and value > last_min
min = value
output[i] = min
print output[k-1]
_
(これは疑似コードですが、Javaで実装するのに十分簡単なはずです)。
全体的な複雑度はO(n*k)
です。つまり、k
が定数であるか、log(n)
よりも小さいことがわかっている場合にのみ、かなりうまく機能します。
プラス面では、それは本当にシンプルなソリューションです。マイナス面では、ヒープソリューションほど効率的ではありません。