私は最近、インタビューに出席し、「10億の数字の中から最大の100の数字を見つけるプログラムを書く」と聞かれました。
私は、配列をO(nlogn)時間の複雑さでソートし、最後の100個の数値を取るという強引な解決策を与えることしかできませんでした。
Arrays.sort(array);
インタビュアーはもっと時間の複雑さを探していました、私は他の解決策をいくつか試しましたが、彼に答えませんでした。より良い時間複雑さの解決策はありますか?
あなたは、最大の100個の数字からなる優先順位の待ち行列を保持し、10億個の数字で繰り返すことができます。待ち行列の最小の数字(待ち行列の先頭)より大きい数字に遭遇したらキューに。
EDIT:Devが指摘したように、優先度キューをヒープで実装した場合、キューへの挿入の複雑さはO(logN)
です。
最悪の場合はbillionlog2(100)
これはbillion
より良いですlog2(billion)
となります。
一般に、N個の数字のセットから最大のK個の数字が必要な場合、複雑さはO(NlogK)
ではなくO(NlogN)
であり、KがNと比較して非常に小さい場合、これは非常に重要になります。
EDIT2:
このアルゴリズムの予想時間はかなり興味深いものです。なぜなら、各反復で挿入が起こるかもしれないし起こらないかもしれないからです。 i番目の数がキューに挿入される確率は、同じ分布からのランダム変数が少なくともi-K
確率変数よりも大きい確率です(最初のk個の数が自動的にキューに追加されます)。この確率を計算するために順序統計( link を参照)を使うことができます。たとえば、数値が{0, 1}
から一様にランダムに選択され、(i-K)番目(iのうち)の期待値は(i-k)/i
であり、この値より大きいランダム変数の可能性は1-[(i-k)/i] = k/i
であるとします。
したがって、予想される挿入数は次のとおりです。
そして予想される実行時間は次のように表すことができます。
(最初のk
要素、次にn-k
比較、および上記の予想される挿入数でキューを生成するk
時間は、それぞれ平均log(k)/2
時間がかかります)
N
がK
と比較して非常に大きい場合、この式はn
よりもNlogK
に非常に近いことに注意してください。質問の場合のように、これはやや直感的ですが、10000回の反復(10億回と比較して非常に小さい)の後でも、数がキューに挿入される可能性は非常に小さいです。
これがインタビューで尋ねられたなら、インタビュアーはおそらくあなたのアルゴリズムの知識だけではなくあなたの問題解決プロセスを見たがっていると思う。
説明はきわめて一般的なので、問題を明確にするためにこれらの数字の範囲や意味を聞いてみることもできます。これをすることはインタビュアーを感動させるかもしれません。たとえば、これらの数字が国内(たとえば中国)内の人々の年齢を表す場合、それははるかに簡単な問題です。生きている人が200歳以上でないという合理的な仮定のもとで、サイズ200のint配列(おそらく201)を使用して、同じ年齢の人の数を1回の反復で数えることができます。ここでインデックスは年齢を意味します。この後、それは100最大数を見つけるためのケーキです。ちなみにこのアルゴはと呼ばれるカウントソート。
とにかく、質問をより具体的かつ明確にすることは、面接であなたにとって良いことです。
あなたはO(n)を取る数を反復することができます
現在の最小値より大きい値を見つけたときはいつでも、サイズ100の循環キューに新しい値を追加します。
その循環キューの最小値が新しい比較値です。そのキューに追加し続けます。いっぱいの場合は、キューから最小値を抽出します。
これは「algorithm」でタグ付けされていることに気付きましたが、おそらく「インタビュー」でもタグ付けされるはずなので、他のいくつかのオプションを除外します。
10億の数字の由来は何ですか?それがデータベースの場合、「値の上限100でテーブルの順序から値を選択する」ことで、作業が非常にうまく行われます - 方言の違いがあるかもしれません。
これは一度限りですか、それとも繰り返されるのでしょうか。繰り返した場合、どのくらいの頻度で?それが1回限りで、データがファイル内にある場合、 'cat srcfile | all'となります。ソート(必要に応じてオプション)ヘッド-100 'は、コンピュータがこの些細な雑用を処理している間にあなたがすぐにあなたが支払われている生産的な仕事をしてもらうでしょう。
それが繰り返される場合は、最初の答えを得て結果を保存/キャッシュするための適切なアプローチを選ぶことをお勧めします。そうすれば、トップ100を継続的に報告できるようになります。
最後に、この考察があります。あなたはエントリーレベルの仕事を探していて、こっけいなマネージャーや将来の同僚と面接していますか?もしそうなら、あなたは相対的な技術的な長所と短所を記述するアプローチのすべての方法を捨てることができます。もっと管理職を探しているのであれば、ソリューションの開発とメンテナンスのコストを気にしてマネージャーのようにアプローチし、「どうもありがとう」と言って、インタビュアーがCSの雑学に集中したい場合はそのままにします。 。彼とあなたはそこに多くの進歩の可能性があるとは考えにくいでしょう。
次のインタビューで頑張ってください。
これに対する私の即時の反応はヒープを使用することですが、一度にすべての入力値を保持することなくQuickSelectを使用する方法があります。
サイズ200の配列を作成し、それを最初の200個の入力値で埋めます。 QuickSelectを実行して低い100を捨て、100の自由な場所を確保してください。次の100個の入力値を読み込み、もう一度QuickSelectを実行してください。 100個のバッチで入力全体を実行するまで続けます。
最後にあなたはトップ100の値を持っています。 N個の値に対して、QuickSelectをおよそN/100回実行しました。各Quickselectのコストは約200倍の定数なので、合計コストは2N倍の定数です。この説明で100になるようにハードワイヤリングしているパラメータのサイズに関係なく、これは私への入力のサイズにおいて直線的に見えます。
クイック選択アルゴリズム を使用すると、(順序で)インデックス[billion-101]の番号を検索してから、その番号を反復してその番号から逸脱する番号を見つけることができます。
array={...the billion numbers...}
result[100];
pivot=QuickSelect(array,billion-101);//O(N)
for(i=0;i<billion;i++)//O(N)
if(array[i]>=pivot)
result.add(array[i]);
このアルゴリズムの時間は2 X O(N) = O(N)(平均的なケースパフォーマンス)
Thomas Jungblutのような2番目のオプションは、次のとおりです。
--- Heap MAXヒープの構築にはO(N)が使われます。そして、最大100個の最大数がヒープの上部に入ります。必要なのはそれらをヒープから取り出すことです(100 XO( Log(N)).
このアルゴリズムの時間は次のとおりです。O(N)+ 100 X O(Log(N)) = O(N)
他のクイック選択の解決策は支持されていませんが、クイック選択ではサイズ100のキューを使用するよりも早く解決策が見つかることに変わりはありません。比較の観点から、クイック選択の実行時間は2n + o(n)です。非常に単純な実装は、
array = input array of length n
r = Quickselect(array,n-100)
result = array of length 100
for(i = 1 to n)
if(array[i]>r)
add array[i] to result
これは平均3n + o(n)の比較になります。さらに、クイック選択によって配列内の最大100項目が右端の100箇所に残されるという事実を使用して、より効率的にすることができます。したがって、実際には、実行時間は2n + o(n)に改善できます。
これは予想される実行時間であり、最悪の場合ではなく、適切なピボット選択戦略(例えば、21個の要素をランダムに選び、その21個の中央値をピボットとして選択する)を使用することによって得られます。任意の小さい定数cに対して最大で(2 + c)nであることを高い確率で保証します。
実際、最適化されたサンプリング戦略(例:sqrt(n)要素をランダムにサンプリングし、99パーセンタイルを選択する)を使用することで、実行時間は(1 + c)n + o(n)になります。任意の小さいcに対して(Kと仮定すると、選択される要素の数はo(n)である)。
一方、サイズ100のキューを使用するにはO(log(100)n)の比較が必要になり、100の対数2は6.6にほぼ等しくなります。
K = o(N)だがKとNの両方が無限大になるサイズNの配列から最大のK個の要素を選択するという、より抽象的な意味でこの問題を考えると、クイック選択バージョンの実行時間は次のようになります。 O(N)とキューバージョンはO(N log K)になるので、この意味でクイックセレクトも漸近的に優れています。
コメントでは、待ち行列解はランダムな入力で予想される時間N + K log Nで実行されると述べました。もちろん、ランダム入力の仮定は、質問で明示的に述べられていない限り、決して有効ではありません。ランダムな順序で配列をたどるようにキューソリューションを作成することもできますが、これには乱数発生器へのN回の呼び出しの追加コストと、入力配列全体の並べ替え、または長さNを含む長さNの新しい配列の割り当てが発生します。ランダムインデックス.
問題によって元の配列の要素を移動できず、メモリの割り当てコストが高いために配列を複製することが選択肢にならない場合、それは別の問題になります。しかし、厳密には実行時間に関しては、これが最善の解決策です。
億の最初の100の数字を取り、それらをソートします。ソース番号が最小の100よりも大きい場合は、10億回繰り返します。ソート順に挿入します。結局のところ、セットのサイズを超えてO(n)に非常に近いものです。
非常に簡単な解決策は、配列を100回繰り返すことです。これはO(n)
です。
最大数を引き出すたびに(そしてその値を最小値に変更して次の繰り返しでそれが見えないようにするか、前の回答のインデックスを追跡する(元の配列はインデックスを追跡することによって)同じ数の倍数))。 100回繰り返した後、あなたは100個の最大数を得ました。
2つの選択肢
(1)ヒープ(priorityQueue)
サイズ100の最小ヒープを維持します。配列をトラバースします。要素がヒープ内の最初の要素より小さくなったら、それを置き換えます。
InSERT ELEMENT INTO HEAP: O(log100)
compare the first element: O(1)
There are n elements in the array, so the total would be O(nlog100), which is O(n)
(2)マップリデュースモデル。
これはhadoopのワードカウントの例とよく似ています。マップジョブ:すべての要素の頻度または出現回数を数えます。還元:トップK要素を取得します。
通常、私は採用担当者に2つの回答をします。彼らが好きなものは何でも与えなさい。もちろん、すべての正確なパラメータを知っておく必要があるため、map reduceコーディングは面倒です。練習しても害はありません。がんばろう。
この質問は、たった1行のC++コードで、(N log Nではなく)N log(100)の複雑さで答えられます。
std::vector<int> myvector = ...; // Define your 1 billion numbers.
// Assumed integer just for concreteness
std::partial_sort (myvector.begin(), myvector.begin()+100, myvector.end());
最後の答えは、最初の100個の要素が配列の最大の100個であることが保証され、残りの要素は順序付けられていないベクトルです。
C++ STL(標準ライブラリ)はこの種の問題にはとても便利です。
注:これが最適な解決策であるとは言っていませんが、インタビューが軽減されたはずです。
@ron tellerの答えに触発された、ここにあなたが望むことをするための必要最低限のCプログラムがあります。
#include <stdlib.h>
#include <stdio.h>
#define TOTAL_NUMBERS 1000000000
#define N_TOP_NUMBERS 100
int
compare_function(const void *first, const void *second)
{
int a = *((int *) first);
int b = *((int *) second);
if (a > b){
return 1;
}
if (a < b){
return -1;
}
return 0;
}
int
main(int argc, char ** argv)
{
if(argc != 2){
printf("please supply a path to a binary file containing 1000000000"
"integers of this machine's wordlength and endianness\n");
exit(1);
}
FILE * f = fopen(argv[1], "r");
if(!f){
exit(1);
}
int top100[N_TOP_NUMBERS] = {0};
int sorts = 0;
for (int i = 0; i < TOTAL_NUMBERS; i++){
int number;
int ok;
ok = fread(&number, sizeof(int), 1, f);
if(!ok){
printf("not enough numbers!\n");
break;
}
if(number > top100[0]){
sorts++;
top100[0] = number;
qsort(top100, N_TOP_NUMBERS, sizeof(int), compare_function);
}
}
printf("%d sorts made\n"
"the top 100 integers in %s are:\n",
sorts, argv[1] );
for (int i = 0; i < N_TOP_NUMBERS; i++){
printf("%d\n", top100[i]);
}
fclose(f);
exit(0);
}
私のマシン(高速SSDを搭載したコアi3)では、25秒かかり、1724がソートされます。この実行のためにdd if=/dev/urandom/ count=1000000000 bs=1
を使ってバイナリファイルを生成しました。
明らかに、ディスクから一度に4バイトしか読まないことにはパフォーマンス上の問題がありますが、これは一例です。プラス面では、非常に少ないメモリが必要です。
簡単な解決策は、優先度キューを使用して、キューに最初の100個の数字を追加し、キュー内の最小の数字を追跡し、次に他の10億個の数字を繰り返し、最大の数字より大きい数字を見つけるたびにプライオリティキューでは、最小の番号を削除し、新しい番号を追加して、再びキューの最小の番号を追跡します。
数字がランダムな順序で並んでいれば、これはきれいに機能します。何十億もの乱数を繰り返し処理するときに、次の数字がこれまでで最大の100の間に入ることは非常にまれだからです。しかし、数字はランダムではないかもしれません。配列がすでに昇順にソートされている場合は、常に常に要素を優先度キューに挿入します。
そのため、最初に配列から100,000の乱数を選択します。遅くなるかもしれないランダムアクセスを避けるために、我々は例えば250の連続した数の400のランダムなグループを加える。このランダムな選択では、残りの数のほとんどがトップ100に含まれていないことを確信できるので、実行時間は10億の数を最大値と比較する単純なループの実行時間に非常に近くなります。
10億個の数字の中から上位100個を見つけるには、 min-heap 100個の要素を使用するのが最善です。
最初に100個までの数字で最小ヒープをプライムします。 min-heapは最初の100個の数字のうち最小のものをルート(一番上)に格納します。
今、あなたが残りの数に沿って進むにつれて、それらをただルート(100の最も小さい)と比較するだけです。
見つかった新しい番号がmin-heapのルートより大きい場合は、ルートをその番号に置き換えます。それ以外の場合は無視します。
Min-heapへの新しい番号の挿入の一部として、ヒープ内の最小番号が一番上(ルート)に来ます。
すべての数値を調べ終わったら、最小ヒープ内で最大の100個の数値が得られます。
Although in this question we should search for top 100 numbers, I will
generalize things and write x. Still, I will treat x as constant value.
nから最大x要素までのアルゴリズム:
戻り値LISTを呼び出します。これはx個の要素の集合です(私の考えではリンクリストにするべきです)
それで、最悪のシナリオは何ですか?
x log(x)+(n-x)(log(x)+ 1)= nlog(x)+ n - x
最悪の場合はO(n)時間です。 +1は、数値がLIST内の最小の数値より大きいかどうかの確認です。平均的な場合の予想される時間は、これらのn要素の数学的分布に依存します。
改善の可能性
このアルゴリズムは最悪のシナリオでは少し改善できますが、平均的な振る舞いを低下させることになる私見(私はこの主張を証明することはできません)。漸近的な振る舞いは同じになります。
このアルゴリズムの改善点は、elementが最小よりも大きいかどうかをチェックしないことです。それぞれの要素に対して、それを挿入しようと試みます、そして、それが最小より小さいならば、それを無視します。最悪のシナリオだけを考えれば、それは素晴らしいことに思えますが
x log(x)+(n-x)log(x)= nlog(x)
オペレーション。
このユースケースでは、これ以上改善は見られません。それでも、あなたは自分自身に尋ねなければなりません - 私がlog(n)回より多くのそして異なるx-esのためにこれをしなければならないならどうでしょうか?明らかに、その配列をO(n log(n))にソートし、それらが必要なときはいつでもx要素を取ります。
最も簡単な解決策は、何十億もの大きな配列をスキャンして、これまでに見つかった最大の100個の値をソートせずに小さな配列バッファーに保持して、このバッファーの最小値を記憶することです。最初に私はこの方法がfordprefectによって提案されたと思いました、しかしコメントで彼は彼がヒープとして実行されている100の数データ構造を仮定すると言いました。バッファ内の最小値よりも大きい新しい数値が見つかるたびに、見つかった新しい値で上書きされ、バッファで現在の最小値が再び検索されます。 10億の数値配列がほとんどの場合ランダムに分布している場合、大きい配列の値は小さい配列の最小値と比較され、破棄されます。ごくわずかな数の分数の場合にのみ、値を小さい配列に挿入する必要があります。そのため、小さい数を保持するデータ構造を操作することの違いは無視できます。少数の要素については、プライオリティキューの使用が実際に私の単純な方法を使用するよりも速いかどうかを判断するのは困難です。
10 ^ 9要素の配列がスキャンされたときに、小さな100要素の配列バッファーに挿入される数を見積もりたいのです。プログラムはこの大規模配列の最初の1000個の要素をスキャンし、最大1000個の要素をバッファーに挿入する必要があります。バッファには、スキャンされた1000個の要素のうち100個の要素、つまりスキャンされた要素の0.1個が含まれています。そのため、大きな配列の値がバッファの現在の最小値よりも大きくなる確率は約0.1であると想定します。このような要素をバッファに挿入する必要があります。これで、プログラムは大きな配列から次の10 ^ 4個の要素をスキャンします。新しい要素が挿入されるたびにバッファの最小値が増えるためです。現在の最小値よりも大きい要素の比率は約0.1であると推定したため、挿入する要素は0.1 * 10 ^ 4 = 1000個です。実際には、バッファに挿入される要素の予想数は少なくなります。この10 ^ 4要素をスキャンした後のバッファ内の数の割合は、これまでにスキャンされた要素の約0.01になります。そのため、次の10 ^ 5の数字をスキャンするときは、0.01 * 10 ^ 5 = 1000以下がバッファに挿入されると仮定します。この議論を続けて、我々は大きな配列の1000 + 10 ^ 4 + 10 ^ 5 + ... + 10 ^ 9〜10 ^ 9要素をスキャンした後に約7000の値を挿入しました。そのため、ランダムなサイズの10 ^ 9個の要素を持つ配列をスキャンするときには、バッファーに10 ^ 4(= 7000切り上げ)以下の挿入があると予想します。バッファに挿入されるたびに、新しい最小値が見つかるはずです。バッファが単純な配列の場合は、新しい最小値を見つけるために100個の比較が必要です。バッファが別のデータ構造(ヒープなど)の場合、最小値を見つけるには少なくとも1回の比較が必要です。大規模配列の要素を比較するには、10 ^ 9個の比較が必要です。したがって、全体としてバッファとして配列を使用する場合は約10 ^ 9 + 100 * 10 ^ 4 = 1.001 * 10 ^ 9の比較、別の種類のデータ構造(ヒープなど)を使用する場合は少なくとも1.000 * 10 ^ 9の比較が必要です。 。したがって、パフォーマンスが比較回数によって決まる場合は、ヒープを使用しても0.1%の増加になります。しかし、100要素のヒープに要素を挿入してから100要素の配列に要素を置き換えるまでの実行時間の違いは何ですか?
理論的レベルで:ヒープに挿入するために必要な比較数。私はそれがO(log(n))であることを知っています、しかし定数の大きさはいくらですか?私
マシンレベルで:配列内のヒープ挿入と線形検索の実行時間に対するキャッシングと分岐予測の影響は何ですか。
実装レベルで:ライブラリまたはコンパイラによって提供されるヒープデータ構造には、どのような追加コストが隠されていますか?
100要素のヒープまたは100要素の配列のパフォーマンスの実際の違いを見積もる前に、これらの質問にいくつか答えなければならないと思います。したがって、実験を行い、実際のパフォーマンスを測定することは理にかなっています。
特に注意してください。 2番目のステップは、並行して計算するのが簡単かもしれません。また、最大の要素が100万個必要な場合も効率的になります。
Time ~ O(100 * N)
Space ~ O(100 + N)
100個の空のスロットの空のリストを作成する
Input-list内のすべての番号に対して
数が最初の数より少ない場合は、スキップします
それ以外の場合はこの番号に置き換えます
次に、隣接するスワップを介して番号をプッシュします。それが次のものより小さくなるまで
リストを返す
注:log(input-list.size) + c < 100
の場合、最適な方法は入力リストをソートしてから最初の100項目を分割することです。
誰かが興味を持った場合に備えて、私はPythonで簡単な解決策を書きました。それはbisect
モジュールとソートされたままの一時的な戻りリストを使います。これはプライオリティキューの実装と似ています。
import bisect
def kLargest(A, k):
'''returns list of k largest integers in A'''
ret = []
for i, a in enumerate(A):
# For first k elements, simply construct sorted temp list
# It is treated similarly to a priority queue
if i < k:
bisect.insort(ret, a) # properly inserts a into sorted list ret
# Iterate over rest of array
# Replace and update return array when more optimal element is found
else:
if a > ret[0]:
del ret[0] # pop min element off queue
bisect.insort(ret, a) # properly inserts a into sorted list ret
return ret
ソートされたリストである100,000,000要素および最悪の場合の入力による使用法:
>>> from so import kLargest
>>> kLargest(range(100000000), 100)
[99999900, 99999901, 99999902, 99999903, 99999904, 99999905, 99999906, 99999907,
99999908, 99999909, 99999910, 99999911, 99999912, 99999913, 99999914, 99999915,
99999916, 99999917, 99999918, 99999919, 99999920, 99999921, 99999922, 99999923,
99999924, 99999925, 99999926, 99999927, 99999928, 99999929, 99999930, 99999931,
99999932, 99999933, 99999934, 99999935, 99999936, 99999937, 99999938, 99999939,
99999940, 99999941, 99999942, 99999943, 99999944, 99999945, 99999946, 99999947,
99999948, 99999949, 99999950, 99999951, 99999952, 99999953, 99999954, 99999955,
99999956, 99999957, 99999958, 99999959, 99999960, 99999961, 99999962, 99999963,
99999964, 99999965, 99999966, 99999967, 99999968, 99999969, 99999970, 99999971,
99999972, 99999973, 99999974, 99999975, 99999976, 99999977, 99999978, 99999979,
99999980, 99999981, 99999982, 99999983, 99999984, 99999985, 99999986, 99999987,
99999988, 99999989, 99999990, 99999991, 99999992, 99999993, 99999994, 99999995,
99999996, 99999997, 99999998, 99999999]
100,000,000要素の場合、これを計算するのに約40秒かかりましたので、10億分の1にするのが怖いのです。ただし、公平を期すために、最悪の場合の入力(皮肉なことに、既に並べ替えられた配列)を入力していました。
まず1000個の要素を取り、それらを最大ヒープに追加します。今すぐ最初の最大100要素を取り出して、どこかに保管してください。今度はファイルから次の900個の要素を選び、最後の100個の最も大きい要素と共にそれらをヒープに追加します。
ヒープから100個の要素を取得し、ファイルから900個の要素を追加するというこのプロセスを繰り返します。
100個の要素からなる最後の選択で、10億個の数字から最大100個の要素が得られます。
私は自分のコードを作りましたが、それが「インタビュアー」なのかどうかわからない
private static final int MAX=100;
PriorityQueue<Integer> queue = new PriorityQueue<>(MAX);
queue.add(array[0]);
for (int i=1;i<array.length;i++)
{
if(queue.peek()<array[i])
{
if(queue.size() >=MAX)
{
queue.poll();
}
queue.add(array[i]);
}
}
別のO(n)アルゴリズム -
アルゴリズムは、消去によって最大の100を見つけます。
それらのバイナリ表現ですべての百万の数を考慮しなさい。最上位ビットから始めます。 MSBが1であるかどうかを見つけることは、ブール演算と適切な数との乗算によってなされ得る。この100万に100以上の1がある場合は、他の数字をゼロで削除します。残りの数字のうち、次の最上位ビットに進みます。削除後も残りの数をカウントし、この数が100を超えている限り続行します。
メジャーなブール演算は、GPUで並行して実行できます。
それはグーグルまたは他の何人かの業界の巨人からの質問です。たぶん次のコードはあなたのインタビュアーによって期待される正しい答えです。時間コストとスペースコストは、入力配列の最大数によって異なります。32ビット整数配列入力の場合、最大スペースコストは4×125Mバイト、時間コストは50×10億です。
public class TopNumber {
public static void main(String[] args) {
final int input[] = {2389,8922,3382,6982,5231,8934
,4322,7922,6892,5224,4829,3829
,6892,6872,4682,6723,8923,3492};
//One int(4 bytes) hold 32 = 2^5 value,
//About 4 * 125M Bytes
//int sort[] = new int[1 << (32 - 5)];
//Allocate small array for local test
int sort[] = new int[1000];
//Set all bit to 0
for(int index = 0; index < sort.length; index++){
sort[index] = 0;
}
for(int number : input){
sort[number >>> 5] |= (1 << (number % 32));
}
int topNum = 0;
outer:
for(int index = sort.length - 1; index >= 0; index--){
if(0 != sort[index]){
for(int bit = 31; bit >= 0; bit--){
if(0 != (sort[index] & (1 << bit))){
System.out.println((index << 5) + bit);
topNum++;
if(topNum >= 3){
break outer;
}
}
}
}
}
}
}
誰が数十億の数字を配列に入れて発砲させる時間があるのかを私は知るでしょう。政府のために働かなければならない。少なくともあなたがリンクリストを持っていたなら、あなたは部屋を作るために5億を動かすことなく中間に数を挿入することができます。さらに良いBtreeは二分探索を可能にします。各比較はあなたの合計の半分を排除します。ハッシュアルゴリズムを使用すると、チェッカーボードのようにデータ構造を移入できますが、スパースデータにはあまり適していません。最善の策は、100個の整数のソリューション配列を用意し、ソリューション配列の最小数を追跡して、元の配列のより大きな数に遭遇したときに置き換えることができるようにすることです。最初はソートされていないと仮定して、元の配列内のすべての要素を調べる必要があります。
改善の可能性あり
ファイルに10億の数字が含まれている場合、それを読むのは本当に長い...
この作業を改善するためにあなたはできる:
あなたはO(n)
の時間でそれをすることができます。リストを繰り返し処理して、任意の時点で見た最大の100個の数字とそのグループの最小値を追跡します。 10よりも小さい新しい数値が見つかったら、それを置き換えて、新しい最小値の100を更新します(これを行うたびにこれを決定するために100の一定時間がかかりますが、これは全体的な分析には影響しません) ).
私はO(N)の議論をたくさん見ているので、私は思考演習のためだけに別の何かを提案します。
これらの数字の性質について既知の情報はありますか?それが本質的にランダムであれば、それ以上先に行かずに他の答えを見てください。あなたは彼らがするより良い結果を得ることはないでしょう。
しかしながら!どんなリスト生成メカニズムも特定の順序でそのリストを生成したかどうかを見てください。それらは、リストの特定の領域または特定の間隔で最大の数の数字が見つかることを確実に知ることができる明確に定義されたパターンになっていますか?それにパターンがあるかもしれません。もしそうなら、例えば、それらが特性のこぶを真ん中にしたある種の正規分布にあることが保証されているならば、定義されたサブセットの間で常に上方傾向を繰り返す、データの真ん中にある時間Tで長期のスパイクを持つおそらくインサイダー取引や機器の故障の発生のように設定されているか、あるいは大災害後の力の分析のようにN番目ごとに「スパイク」が発生している場合は、チェックしなければならないレコードの数を減らすことができます。
とにかく思考のためのいくつかの食べ物があります。多分これはあなたが将来のインタビュアーに思慮深い答えを与えるのを助けるでしょう。このような問題に対して誰かが私にそのような質問をしてくれたら私は感動するでしょう - それは彼らが最適化を考えていることを教えてくれるでしょう。最適化する可能性が常にあるとは限らないことを認識してください。
複雑さはO(N)
最初に、この配列の最初の要素をN個の値の最初の要素として初期化して100インチの配列を作成し、現在の要素のインデックスを別の変数で追跡し、それをCurrentBigと呼びます。
N個の値を繰り返します
if N[i] > M[CurrentBig] {
M[CurrentBig]=N[i]; ( overwrite the current value with the newly found larger number)
CurrentBig++; ( go to the next position in the M array)
CurrentBig %= 100; ( modulo arithmetic saves you from using lists/hashes etc.)
M[CurrentBig]=N[i]; ( pick up the current value again to use it for the next Iteration of the N array)
}
完了したら、CurrentBigの100倍の100を法とするMの配列を出力します。-)学生のために:コードが終了する直前にコードの最後の行が有効なデータを切り捨てないことを確認してください