私の友人の一人がこのインタビューの質問をされました-
「特定の時点で上位100の最大数を返すためにデータ構造を維持する必要がある、数の無限リストから入ってくる数の一定のフローがあります。すべての数は整数のみであると想定してください。」
これは簡単です。ソートされたリストを降順で保持し、そのリストで最も小さい番号でトラックを維持する必要があります。取得した新しい数がその最小数より大きい場合は、その最小数を削除し、必要に応じてソート済みリストに新しい数を挿入する必要があります。
その後、質問が拡張されました-
「挿入の注文がO(1)であることを確認できますか?それは可能ですか?」
私が知っている限りでは、リストに新しい数値を追加し、任意のソートアルゴリズムを使用してそれを再度ソートする場合でも、クイックソートの場合、O(logn))が最善です(と思います)。私の友人はそれは不可能だと言ったが、彼は確信していなかった、彼はリストではなく他のデータ構造を維持するように頼んだ。
私はバランスのとれた二分木を考えましたが、それでも1の順序で挿入を取得することはできません。そのため、今も同じ質問があります。上記の問題のために1の順に挿入できるようなデータ構造があるか、まったく不可能かを知りたい。
Kが知りたい最高の数(例では100)であるとします。次に、O(k)
でもある新しい数値をO(1)
に追加できます。なぜならO(k*g) = O(g) if k is not zero and constant
だからです。
リストはソートしないでください。新しい番号を挿入するかどうかを判断するのに時間がかかりますが、挿入はO(1)になります。
これは簡単。定数のリストのサイズ、したがってリストのソート時間は一定です。一定時間内に実行される操作はO(1)と呼ばれます。したがって、固定サイズのリストの場合、リストのソートはO(1)です。
100の数値を渡すと、次の数値に対して発生する最大のコストは、その数値が最大の100の数値であるかどうかを確認するためのコスト(ラベルにCheckTimeを付けます)と入力するコストです。それをそのセットに入れて、最低のものを排出します(これをEnterTimeと呼びましょう)。これは、一定の時間(少なくとも有界数の場合)、またはO(1)です。
Worst = CheckTime + EnterTime
次に、数値の分布がランダムである場合、平均コストはより多くの数値を減少させます。たとえば、101番目の数を最大セットに入力する必要がある確率は100/101で、1000番目の数の確率は1/10、n番目の数の確率は100/nです。したがって、平均コストの方程式は次のようになります。
Average = CheckTime + EnterTime / n
したがって、nが無限に近づくと、CheckTimeのみが重要になります。
Average = CheckTime
数値がバインドされている場合、CheckTimeは定数であり、したがってO(1)時間です。
数が束縛されていない場合、チェック時間は数が増えると増加します。理論的には、これは、最大セットの最小数が十分に大きくなると、より多くのビットを考慮する必要があるため、チェック時間が長くなるためです。それはそれが一定の時間よりわずかに高くなるように思わせます。ただし、nが無限に近づくと、次の数値が最も高いセットに含まれる可能性がゼロに近づき、さらに多くのビットを考慮する必要がある可能性も0に近づくと主張することもできます。 O(1)時間の引数。
私はポジティブではありませんが、私の腸はO(log(log(n)))時間だと言っています。これは、最小数が増加する可能性が対数的であり、各チェックで考慮する必要があるビット数も同様に対数的であるためです。私は他の人々がこれを引き受けることに興味があります。
バイナリヒープツリー を知っていれば、これは簡単です。バイナリヒープは、平均一定時間O(1)での挿入をサポートします。また、最初のx要素に簡単にアクセスできます。
インタビュアーが「各着信番号が一定の時間で処理されることを確認できますか?」ソートされていないリストを使用したり、バブルソートを使用したりしたとしても、そうなります。この場合、トリッキーな質問であるか、間違って覚えていない限り、質問はあまり意味がありません。
私はインタビュアーの質問が有意義だったと思います、彼は何かをどのようにするかを尋ねていなかったということですO(1)これはすでに非常に明白です。
質問アルゴリズムの複雑さは、入力のサイズが無限に大きくなる場合にのみ意味があり、ここで大きくなる可能性がある唯一の入力は100(リストのサイズ)です。本当の質問は「トップNの支出が確実に得られるかO(1)時間あたりの時間(O(N)友人の解決策)、それは可能ですか?」.
最初に頭に浮かぶのは、並べ替えを数えることです。これは、O(1)時間あたりの時間あたりのトップN問題の計算時間O(m)スペース、ここでmは着信番号の範囲の長さなので、可能です。
挿入時間が一定の フィボナッチヒープ で実装された最小優先度キューを使用します。
1. Insert first 100 elements into PQ
2. loop forever
n = getNextNumber();
if n > PQ.findMin() then
PQ.deleteMin()
PQ.insert(n)
タスクは明らかに、必要な数のリストの長さNでO(1))であるアルゴリズムを見つけることです。したがって、上位100の数または10000の数が必要かどうかは関係ありません。 、挿入時間はO(1)である必要があります。
ここでの秘訣は、リスト挿入のO(1)要件が言及されているが、質問は整数空間における検索時間の順序について何も言わなかったが、これもO(1)で作成できます。解決策は次のとおりです。
キーの数値と値のリンクリストポインターのペアを含むハッシュテーブルを準備します。ポインターの各ペアは、リンクされたリストシーケンスの開始と終了です。これは通常、次に1つの要素になります。リンクされたリスト内のすべての要素は、次に大きい番号の要素の隣に移動します。したがって、リンクされたリストには、必要な数のソートされたシーケンスが含まれています。最小数のレコードを保持してください。
ランダムストリームから新しい数xを取得します。
最後に記録された最小値より高いですか?はい=>ステップ4、いいえ=>ステップ2
取得した数値でハッシュテーブルをヒットします。エントリーはありますか?はい=>ステップ5.いいえ=>新しい数値x-1を取り、このステップを繰り返します(これは単純な下向き線形検索です。ここで我慢してください。これは改善することができ、方法を説明します)
ハッシュテーブルから取得したリスト要素で、リンクリストの要素の直後に新しい番号を挿入します(そしてハッシュを更新します)
記録された最小の番号lを取得します(そしてそれをハッシュ/リストから削除します)。
取得した数値でハッシュテーブルをヒットします。エントリーはありますか?はい=>ステップ8.いいえ=>新しい数値l + 1をとり、このステップを繰り返します(これは単純な上方線形検索です)。
正のヒットがあると、その数は新しい最小数になります。ステップ2に進む
重複する値を許容するために、ハッシュは実際には、重複する要素のリンクリストシーケンスの開始と終了を維持する必要があります。したがって、特定のキーで要素を追加または削除すると、ポイントされる範囲が増減します。
ここでの挿入はO(1)です。言及された検索は、O(数値間の平均差)のようなものだと思います。平均差は、数値スペースのサイズとともに増加しますが、数値リストの必要な長さとともに減少します。
したがって、数値空間が大きく(たとえば、4バイトのint型の場合、0から2 ^ 32-1)、N = 100の場合、線形検索戦略はかなり貧弱です。このパフォーマンスの問題を回避するために、適切なキーを作成するために数値をより大きな値(1秒、10秒、100秒、1000秒など)に丸めるハッシュテーブルの並列セットを保持できます。このようにして、必要な検索をより迅速に実行するためにギアを上下に動かすことができます。次に、パフォーマンスはO(log numberrange)になると思います。これは一定です。つまり、O(1)でもあります。
これをより明確にするために、手元に197という番号があるとします。 10のハッシュテーブルをヒットし、 '190'で最も近い10に丸められます。何か?いいえ。つまり、120になるまで10秒でダウンします。次に、1秒のハッシュテーブルで129から始めて、何かに当たるまで128、127を試してください。リンクされたリストで197を挿入する場所を見つけました。それを挿入するときに、1sハッシュテーブルを197エントリで、10sハッシュテーブルを190で、100sを100で、なども更新する必要があります。ほとんどの手順ここで行う必要があるのは、数値範囲のログの10倍です。
詳細の一部が間違っている可能性がありますが、これはプログラマーの交換であり、コンテキストはインタビューであったため、上記がその状況に対して十分説得力のある答えであることを願っています。
[〜#〜] edit [〜#〜]並列ハッシュテーブルスキームを説明するためにここに追加の詳細を追加し、それが私が言及した貧弱な線形検索がO(1)検索。もちろん、次の最小数を検索する必要がないことにも気づきました。これは、最小数のハッシュテーブルを探して次へ進むことで、直接それに進むことができるためです。素子。
100個の数値は、サイズが100の配列に簡単に格納できます。当面のタスクを考えると、ツリー、リスト、またはセットは過剰です。
着信番号が配列の最小値(=最後)より大きい場合は、すべてのエントリを実行します。新しい数よりも小さい最初のものが見つかったら(空想検索を使用してそれを行うことができます)、配列の残りの部分を実行し、各エントリを1つ下に押し下げます。
リストは最初からソートされているため、ソートアルゴリズムを実行する必要はまったくありません。これはO(1)です。
数値が整数などの固定データ型であると想定できますか?その場合は、追加されたすべての数値の集計を保持します。これはO(1)操作です。
VB.Netコード:
Const Capacity As Integer = 100
Dim Tally(Integer.MaxValue) As Integer ' Assume all elements = 0
Do
Value = ReadValue()
If Tally(Value) < Capacity Then Tally(Value) += 1
Loop
リストを返すときは、好きなだけ時間をかけることができます。リストの最後から簡単に削除して、記録された最高100の値の新しいリストを作成します。これはO(n)演算ですが、それは無関係です。
Dim List(Capacity) As Integer
Dim ListCount As Integer = 0
Dim Value As Integer = Tally.Length - 1
Dim ValueCount As Integer = 0
Do Until ListCount = List.Length OrElse Value < 0
If Tally(Value) > ValueCount Then
List(ListCount) = Value
ValueCount += 1
ListCount += 1
Else
Value -= 1
ValueCount = 0
End If
Loop
Return List
編集:実際、それが固定データ型であるかどうかは問題ではありません。メモリ(またはハードディスク)の消費に課せられる制限がない場合、正の整数の任意の範囲でこれを機能させることができます。
Binary Max-Heapを使用できます。最小ノードへのポインターを追跡する必要があります(不明またはnullの可能性があります)。
最初に、最初の100個の数値をヒープに挿入します。最大は上部になります。これが行われた後は、常にそこに100個の数字を保持します。
次に、新しい番号を取得したとき:
if(minimumNode == null)
{
minimumNode = findMinimumNode();
}
if(newNumber > minimumNode.Value)
{
heap.Remove(minimumNode);
minimumNode = null;
heap.Insert(newNumber);
}
残念ながらfindMinimumNode
はO(n)であり、挿入ごとに1回のコストが発生します(ただし、挿入中には発生しません)。最小ノードの削除と新しいノードの挿入は、ヒープの下部に向かう傾向があるため、平均してO(1)です。
Binary Min-Heapを使用すると、最小値が一番上にあり、比較のために最小値を見つけるのに最適ですが、最小値を新しい最小値>> minに置き換える必要がある場合は、最悪です。これは、最小ノード(常にO(logN))を削除してから、新しいノード(平均O(1))を挿入する必要があるためです。したがって、O(logN)これはMax-Heapより優れていますが、O(1)ではありません。
もちろん、Nが定数の場合、常にO(1)になります。 :)