無限リストから最高の100個を取得


53

私の友人の一人がこのインタビューの質問を受けました-

「ある一定の時点で上位100個の数字を返すようにデータ構造を維持する必要がある数字の無限リストからの数字の流れが絶え間なくあります。すべての数字は整数のみであると仮定します。」

これは簡単です。ソートされたリストを降順で保持し、そのリスト内の最小数を追跡する必要があります。取得した新しい数がその最小数よりも大きい場合、その最小数を削除し、必要に応じてソート済みリストに新しい数を挿入する必要があります。

その後、質問が延長されました-

「挿入の順序がO(1)であることを確認できますか?可能ですか?」

私が知っている限りでは、リストに新しい番号を追加し、並べ替えアルゴリズムを使用して再度並べ替えても、クイックソートのO(logn)が最適です(私は思う)。だから私の友人はそれは不可能だと言った。しかし、彼は納得しておらず、リストではなく他のデータ構造を維持するよう求めました。

私はバランスの取れた二分木を考えましたが、そこでも1の順序で挿入されません。だから私も同じ質問をしています。上記の問題に対して1の順序で挿入できるデータ構造があるか、まったく不可能であるかを知りたい。


19
たぶんこれは私が質問を誤解しているだけかもしれませんが、なぜソートされたリストを保持する必要があるのですか?なぜ最小の番号を追跡するだけでなく、それよりも大きい番号に遭遇した場合は、リストをソートせずに、最小の番号を削除して新しい番号を入力してください。それはあなたにO(1)を与えるでしょう。
江戸堂

36
@EdoDodo-そして、その操作の後、新しい最低番号がどのようにわかるのですか?
-Damien_The_Unbeliever

19
リスト[O(100 * log(100))= O(1)]を並べ替えるか、最小[O(100)= O(1)]で線形検索を実行して、新しい最小数を取得します。リストは一定のサイズであるため、これらの操作はすべて一定時間です。
ランダム832

6
リスト全体を並べ替える必要はありません。最高または2番目に大きい数が何であるかは気にしません。一番低いものが何であるかを知る必要があります。したがって、新しい番号を挿入した後は、100個の番号をトラバースして、どれが最も低いかを確認します。それは一定時間です。
トムザイチ

27
操作の漸近的順序は、問題のサイズが際限なく大きくなる場合にのみ興味深いものです。あなたの質問から、どの量が際限なく増えているのか非常に不明確です。サイズが100に制限されている問題の漸近的な順序を尋ねているようです。それは賢明な質問でさえありません。何かが無限に成長しなければなりません。質問が「O(1)時間で、上位100ではなく上位nを維持するためにできますか?」質問は賢明です。
エリックリッパー

回答:


35

kは、知りたい最高の数(例では100)であるとしましょう。その後、あなたは、新しい番号を追加することができますO(k)もあるしO(1)。なぜならO(k*g) = O(g) if k is not zero and constant


6
O(50)はO(n)であり、O(1)ではありません。時間が100となる場合手段10000、50は5000となってはならないこと、Nの値に依存しないことがO(1)時間の手段の長さNのリストに挿入

18
@hamstergene-しかし、この質問の場合N、ソートされたリストのサイズ、またはこれまでに処理されたアイテムの数ですか?10000アイテムを処理し、リストの上位100アイテムを保持する場合、または1000000000アイテムを処理し、ソートリストの上位100アイテムを保持する場合、そのリストの挿入コストは変わりません。
-Damien_The_Unbeliever

6
@hamstergene:その場合、基本が間違っていました。ウィキペディアのリンクには、プロパティ(「定数による乗算」)がありますO(k*g) = O(g) if k not zero and constant。=> O(50*1) = O(1)
duedl0r

9
duedl0rは正しいと思います。問題を減らして、最小値と最大値のみが必要だとしましょう。最小値と最大値が2であるため、これはO(n)ですか?(n = 2)。No. 2は問題の定義の一部です。定数なので、O(k * something)のakはO(something)
xanatos

9
@hamstergene:どの機能について話しているのですか?値100は...私にはかなりの定数を思わ
duedl0r

19

リストを並べ替えないでください。新しい番号を挿入するかどうかを判断するには時間がかかりますが、挿入はO(1)になります。


7
これは、他に何もなければあなたにスマートアレック賞を授与すると思います。* 8 ')
マークブース

4
@エミリオ、あなたは技術的に正しいです-そしてもちろんそれは正しい種類の最高です...-
ガレス

1
ただし、100個の数字のうち最も小さい数字を保持することもできます。その後、O(1)に挿入する必要があるかどうかを決定することもできます。次に、番号を挿入するときにのみ、新しい最低番号を検索する必要がありますか。しかし、それは、新しい番号ごとに発生する挿入を決定するよりもまれに発生します。
アンドレイヴァイナII

12

これは簡単です。定数のリストのサイズ。したがって、リストのソート時間は一定です。一定時間で実行される操作は、O(1)と呼ばれます。したがって、リストのソートは、固定サイズのリストのO(1)です。


9

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)))時間であると言います。これは、最小数が増加する可能性が対数であり、各チェックで考慮する必要があるビット数が対数であるためです。他の人がこれに取り組むことに興味があります。


リストがarbitrary意的であることを除いて、それが増え続ける数のリストである場合はどうでしょうか?
dan_waterworth

@dan_waterworth:無限リストが任意であり、たまたま増加している場合(オッズは1 /∞になります!)、それはCheckTime + EnterTime各数値の最悪のシナリオに適合します。これは、数字だけが無制限である場合に理にかなっている、などCheckTimeEnterTimeなり増加少なくとも対数数字の大きさの増加による両方。
Briguy37

1
数字はランダムではなく、任意です。オッズについて話すのは意味がありません。
dan_waterworth

@dan_waterworth:数字はarbitrary意的だとあなたは二度言いました。これはどこから入手していますか?また、ランダムなケースから始まる任意の数値に統計を適用し、アービターについての詳細を知っていると、精度を向上させることができると思います。たとえば、あなたがアービターである場合、たとえば私がアービターだった場合よりも、増え続ける数字を選択する可能性が高くなるようです;)
Briguy37

7

Binary Heap Treesを知っていれば、これは簡単です。バイナリヒープは、平均一定時間O(1)での挿入をサポートします。また、最初のx要素に簡単にアクセスできます。


不要な要素を保存する理由 (低すぎる値)カスタムアルゴリズムがより適切であるようです。最も低い値より高くない場合、値を「追加しない」ことはできません。
スティーブンジュリス

わかりませんが、私の直感では、ヒープ(何らかのフレーバー)がこれをかなりうまく引き出せると教えています。彼がそうするためにすべての要素を保持しなければならないという意味ではありません。私はそれを調査しませんでしたが、「気分が良い」(TM)。
リグ

3
ヒープは、mレベルより下のすべてを破棄するように変更できます(バイナリヒープおよびk = 100の場合、ノード数= 2 ^ m-1であるため、mは7になります)。これにより速度は低下しますが、一定の時間で償却されます。
プルーター

3
バイナリのmin-heapを使用した場合(トップが常に最小であり、常にチェックしているため)、新しい数> minを見つけた場合、新しいものを挿入する前にトップの要素を削除する必要があります。ツリーのすべてのレベルを一度走査する必要があるため、最上部(最小)要素を削除するとO(logN)になります。したがって、技術的には、挿入が平均O(1)であるのは事実だけです。なぜなら、実際には、数が> minになるたびにO(logN)であるためです。
スコットホイットロック

1
@Plutor、あなたはバイナリヒープがあなたに与えないといういくつかの保証を仮定しています。これをバイナリツリーとして視覚化すると、左ブランチの各要素が右ブランチのどの要素よりも小さい場合がありますが、最小要素がルートに最も近いと仮定しています。
ピーターテイラー

6

質問によってインタビュアーが「各着信番号が一定時間で処理されることを確認できますか?」ソートされていないリスト、バブルソートなどを使用した場合でもそうです。この場合、質問はトリッキーな質問であるか、覚えていない限り、あまり意味がありません。

インタビュアーの質問は意味のあるものだったと思います。O(1)になる方法を尋ねていたわけではないのは明らかです。

アルゴリズムの複雑さを疑問視するのは、入力のサイズが無制限に増加する場合にのみ意味があり、ここで増加できる入力は100(リストサイズ)だけであるためです。本当の質問は「上位Nが数字ごとにO(1)時間を費やすことを確認できますか(友人のソリューションのようにO(N)ではありません)、それは可能ですか?」

最初に頭に浮かぶのは、ソートをカウントすることです。これは、O(m)スペースを使用する代償として、上位N問題の数ごとにO(1)時間の複雑さを買います。ここで、mは着信番号の範囲の長さです。はい、可能です。


4

一定の挿入時間を持つフィボナッチヒープで実装された最小優先度キューを使用します。

1. Insert first 100 elements into PQ
2. loop forever
       n = getNextNumber();
       if n > PQ.findMin() then
           PQ.deleteMin()
           PQ.insert(n)

4
「操作は、O(log n)償却時間内に最小作業を削除および削除する」ため、これにより、保存するアイテムの量はO(log k)どこkになります。
スティーブンジュリス

1
これは、削除の最小値O(log n)(Wikipediaによる)で動作するため、「スマートアレック賞」と呼ばれるエミリオの答えと違いはありません。
ニコール

@Renesisエミリオの答えは、最小を見つけるために、O(K)になり、鉱山はO(ログK)である
ゲイブMoothart

1
@Gabeまあまあ、私は原則として意味します。言い換えれば、100を定数としない場合、この答えも一定の時間ではありません。
ニコール

@Renesis回答から(誤った)ステートメントを削除しました。
ゲイブモーザート

2

タスクは明らかに、必要な数のリストの長さNでO(1)であるアルゴリズムを見つけることです。したがって、上位100の番号または10000の番号が必要かどうかは関係ありません。挿入時間はO(1)になります。

ここでの秘trickは、リストの挿入に関してそのO(1)要件が言及されているが、質問は整数空間での検索時間の順序については何も言わなかったが、これはO(1)同じように。その場合の解決策は次のとおりです。

  1. キーの数値と値のリンクリストポインターのペアを含むハッシュテーブルを用意します。ポインタの各ペアは、リンクリストシーケンスの開始と終了です。これは通常、次の要素の1つの要素になります。リンクされたリスト内のすべての要素は、次に大きい番号の要素の隣に移動します。したがって、リンクリストには、必要な番号のソートされたシーケンスが含まれます。最小の番号のレコードを保持します。

  2. ランダムストリームから新しい数値xを取得します。

  3. 最後に記録された最低数よりも高いですか?はい=>ステップ4、いいえ=>ステップ2

  4. 取得した数字でハッシュテーブルをヒットします。エントリーはありますか?はい=>ステップ5。

  5. ハッシュテーブルから取得したばかりのリスト要素を使用して、リンクリストの要素の直後に新しい番号を挿入します(そしてハッシュを更新します)。

  6. 記録された最小の番号lを取得します(そしてハッシュ/リストから削除します)。

  7. 取得した数字でハッシュテーブルをヒットします。エントリーはありますか?はい=>ステップ8。いいえ=>新しい数値l + 1を取得して、このステップを繰り返します(これは単純な上向き線形検索です)。

  8. ポジティブヒットの場合、数値は新しい最低の数値になります。ステップ2に進む

重複する値を許可するために、ハッシュは実際には、重複する要素のリンクリストシーケンスの開始と終了を維持する必要があります。したがって、指定されたキーで要素を追加または削除すると、指す範囲が増減します。

ここでの挿入はO(1)です。言及されている検索は、O(数値間の平均差)のようなものだと思います。平均差は、数値スペースのサイズとともに増加しますが、数値リストの必要な長さとともに減少します。

したがって、数値スペースが大きい場合(たとえば、4バイトのint型、0〜2 ^ 32-1)、N = 100の場合、線形検索戦略はかなり貧弱です。このパフォーマンスの問題を回避するために、ハッシュテーブルの並列セットを保持できます。この場合、適切なキーを作成するために、数値がより大きな数値(1秒、10秒、100秒、1000秒)に丸められます。このようにして、ギアを上げ下げして、必要な検索をより迅速に実行できます。パフォーマンスはO(log numberrange)になると思いますが、これは一定、つまりO(1)でもあります。

これを明確にするために、197番を手に入れると想像してください。'190'で10のハッシュテーブルをヒットすると、最も近い10に丸められます。何か?いいえ。だから、120と言うまで10秒でダウンします。1秒のハッシュテーブルで129から始めて、何かをヒットするまで128、127を試すことができます。リンクリストのどこに番号197を挿入するかがわかりました。それを入力しながら、1のハッシュテーブルを197のエントリで更新し、10のハッシュテーブルを番号190、100で100などに更新する必要があります。ここで行う必要があるのは、番号範囲のログの10倍です。

詳細の一部は間違っていたかもしれませんが、これはプログラマーのやり取りであり、コンテキストはインタビューであったため、上記がその状況に十分な説得力のある答えであることを願っています。

編集並列ハッシュテーブルスキームと、それがどのように私が言及した貧弱な線形検索をO(1)検索に置き換えることができるかを説明するために、ここにいくつかの詳細を追加しました。もちろん、次に小さい番号のハッシュテーブルを調べて次の要素に進むことで、その番号に直接進むことができるため、次に小さい番号を検索する必要がないことにも気付きました。


1
検索は挿入関数の一部である必要があります-それらは独立した関数ではありません。検索はO(n)なので、挿入関数もO(n)です。
カークブロードハースト

いいえ。私が説明した戦略を使用すると、より多くのハッシュテーブルを使用して、より迅速に数値空間を走査できます。これはO(1)です。もう一度私の答えを読んでください。
ベネディクト

1
@Benedict、あなたの答えは、ステップ4と7に線形検索があることを非常に明白に述べています。線形検索はO(1)ではありません。
ピーターテイラー

はい、できますが、後で対処します。残りを実際に読んでください。必要に応じて、回答を編集して、明確にするようにします。
ベネディクト

@Benedictあなたは正しいです-検索を除いて、あなたの答えはO(1)です。残念ながら、このソリューションは検索なしでは機能しません。
カークブロードハースト

1

数値が整数などの固定データ型であると想定できますか?その場合、追加されるすべての数字の集計を保持します。これはO(1)操作です。

  1. 可能な限り多くの要素を持つ配列を宣言します。
  2. ストリーミングされている各番号を読み取ります。
  3. 数を集計します。あなたがそれを決して必要としないので、その数がすでに100回集計されているならば、それを無視してください。これにより、オーバーフローが無限に集計されるのを防ぎます。
  4. 手順2から繰り返します。

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

編集:実際には、それが固定データ型であるかどうかは実際には問題ではありません。メモリ(またはハードディスク)の消費に制限が課されていないことを考えると、正の整数の任意の範囲でこの作業を行うことができます。


1

サイズ100の配列に100個の数字を簡単に保存できます。手元のタスクを考えると、ツリー、リスト、またはセットはどれも過剰です。

着信番号が配列の最小(=最後)よりも大きい場合、すべてのエントリを実行します。新しい番号よりも小さい最初のものを見つけたら(空想検索を使用してそれを行うことができます)、配列の残りを実行し、各エントリを1つずつ「押し下げ」ます。

リストは最初からソートされたままなので、ソートアルゴリズムを実行する必要はまったくありません。これはO(1)です。


0

バイナリMax-Heapを使用できます。最小ノード(不明/ヌルの可能性がある)へのポインターを追跡する必要があります。

最初の100個の数字をヒープに挿入することから始めます。最大値は上部にあります。これが行われた後、あなたは常にそこに100個の数字を保持します。

その後、新しい番号を取得すると:

if(minimumNode == null)
{
    minimumNode = findMinimumNode();
}
if(newNumber > minimumNode.Value)
{
    heap.Remove(minimumNode);
    minimumNode = null;
    heap.Insert(newNumber);
}

残念ながらfindMinimumNodeO(n)であり、挿入ごとに1回コストが発生します(ただし、挿入中はそうではありません:)。最小ノードを削除し、新しいノードを挿入すると、平均してO(1)になります。これは、それらがヒープの下部に向かう傾向があるためです。

逆にバイナリミニヒープでは、最小値が一番上にあり、比較のために最小値を見つけるのに最適ですが、最小値を> minの新しい数値で置き換える必要がある場合は不便です。これは、最小ノード(常にO(logN))を削除してから、新しいノード(平均O(1))を挿入する必要があるためです。したがって、O(logN)はMax-Heapよりも優れていますが、O(1)はありません。

もちろん、Nが一定の場合、常にO(1)になります。:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.