web-dev-qa-db-ja.com

Java、配列からK番目に大きい値を見つける

私は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に基づいたその実装は何ですか?

19
Hesam

彼は実際にあなたにすべての答えを与えました。単なるヒントではありません。

そして、あなたの理解はmax heapに基づいています。 min heapではありません。そして、その働きは一目瞭然です。

最小ヒープでは、ルートの値は最小(子より少ない)です。

したがって、必要なのは、配列を反復処理してK要素をmin heapに入力することです。これが完了すると、ヒープにはルートの最下位が自動的に含まれます。

次に、配列から読み取った(next)要素ごとに、->値が最小ヒープのルートより大きいかどうかを確認します。 ->はいの場合、最小ヒープからルートを削除し、それに値を追加します。

配列全体をトラバースした後、min heapのルートには、kth番目に大きい要素が自動的に含まれます。

そして、ヒープ内の他のすべての要素(正確にはk-1要素)はkより大きくなります。

19
Codebender

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_です。ヒープの上部を削除して新しい要素を挿入する必要がある要素。

5
YoungHobbit

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

実装メモ:この実装は、エンキューおよびデキューのメソッド(offerpollremove()O(log(n))時間を提供しますおよびadd); remove(Object)およびcontains(Object)メソッドの線形時間。検索メソッド(peekelement、およびsize)の一定時間。

Forループはn回実行され、上記のアルゴリズムの複雑さはO(nlogn)です。

2
i_am_zero

配列/ストリームの要素数が不明な場合は、ヒープベースのソリューションが最適です。しかし、それらが有限であるにもかかわらず、線形時間で最適化されたソリューションが必要な場合はどうでしょうか。

ここ で説明したクイックセレクトを使用できます。

配列= [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番目)を見つけたかどうかを確認し続けます。見つかった場合は、それ以外の場合は続行します。

0
rai.skumar

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)よりも小さいことがわかっている場合にのみ、かなりうまく機能します。

プラス面では、それは本当にシンプルなソリューションです。マイナス面では、ヒープソリューションほど効率的ではありません。

0
njzk2