web-dev-qa-db-ja.com

与えられたもののうち40億の整数ではない整数を見つめる

それはインタビューの質問です。

40億の整数を持つ入力ファイルを考えて、ファイルに含まれていない整数を生成するためのアルゴリズムを提供します。 1 GBのメモリがあるとします。メモリが10 MBしかない場合にどうするかをフォローアップします。

私の分析:

ファイルのサイズは4×10です9×4バイト= 16 GB。

外部ソートができるので、整数の範囲を知ることができます。私の質問は、ソートされた大きな整数セットで欠けている整数を検出するための最良の方法は何ですか?

私の理解(すべての答えを読んだ後):

32ビット整数について話していると仮定します。 2 ^ 32 = 4 * 10があります9 異なる整数.

ケース1:1 GB = 1 * 109 * 8ビット= 80億ビットメモリ。

解決策:1つの異なる整数を表す1ビットを使用すれば、それで十分です。並べ替えは必要ありません。実装:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

ケース2:10 MBメモリ= 10 * 106 * 8ビット= 8000万ビット

解決策:すべての可能な16ビット接頭辞に対して、2 ^ 16の整数= 65536があります。2^ 16 * 4 * 8 = 200万ビットが必要です。 65536個のバケツを作る必要があります。最悪の場合は40億の整数がすべて同じバケットに属しているため、バケットごとにすべての可能性を保持する4バイトが必要です。

  1. ファイルを最初に通過して各バケットのカウンターを作成します。
  2. バケットをスキャンし、ヒット数が65536未満の最初のバケットを見つけます。
  3. ステップ2でファイルの2回目のパスまでで見つかった高位16ビット接頭部を持つ新しいバケットを作成します。
  4. 手順3で作成したバケットをスキャンし、ヒットしなかった最初のバケットを見つけます。

コードは上記のものと非常によく似ています。

結論:ファイルパスを増やすことでメモリを削減しました。


遅く到着した人への説明:質問によれば、ファイルに含まれていない整数が1つだけあるということではありません。ただし、コメントスレッド内の多くのコメントは、そのタスクのバリエーションについてです。残念ながら、コメントスレッドにそれを導入したコメントは、後でその作者によって削除されました。とても混乱します。ごめんなさい。

675
SecureFish

"integer"が32ビットを意味すると仮定すると:10 MBのスペースがあれば、入力ファイルに含まれる数字の数を数えるのに十分です。入力ファイルの1回のパスで可能なすべての16ビットプレフィックスに対して、任意の16ビットプレフィックス。少なくとも1つのバケットが2 ^ 16回未満ヒットしたことになります。 2回目のパスを実行して、そのバケット内の使用可能な番号のうちどれがすでに使用されているかを調べます。

それが32ビット以上を意味するが、それでも境界サイズの場合:上記のようにして、(符号付きまたは符号なしの)外にあるすべての入力数を無視します。 ;あなたの選択)32ビットの範囲。

"integer"が数学的整数を意味する場合:入力を1回読んでからその値を追跡します。 最大数 あなたが今まで見た中で最も長い数の長さ。完了したら、出力 最大プラス1 もう1桁の乱数。 (ファイル内の数字の1つは、正確に表現するために10 MB以上を要する2進数ですが、入力がファイルの場合は、少なくともの長さを表すことができますそれに収まるものすべての)。

521
Henning Makholm

統計的に知識のあるアルゴリズムは、決定論的アプローチより少ないパスでこの問題を解決します。

非常に大きな整数が許可されている場合、O(1)時間内で一意である可能性が高い数値を生成できます。 GUID のような疑似乱数128ビット整数は、セット内の既存の40億個の整数の1つと1回未満の衝突でしか衝突しません。 640億ケースごとに。

整数が32ビットに制限されている場合、10 MBをはるかに下回るサイズを使用して、1回のパスで一意と思われる数を生成できます。疑似乱数の32ビット整数が40億の既存の整数の1つと衝突する可能性は、約93%(4e9/2 ^ 32)です。 1000個の疑似乱数整数がすべて衝突する確率は、1兆2,000億億分の1以下です(1の衝突確率^ 1000)。そのため、プログラムが1000個の疑似乱数候補を含むデータ構造を維持し、候補から一致を排除して既知の整数を反復処理する場合、ファイル内にない整数を少なくとも1つ見つけるのは確実です。

194
Ben Haley

この問題についての詳細な議論は Jon Bentley 「コラム1。カキの割れ」Programming PearlsAddison-Wesley pp.3-10

Bentleyでは、外部ソート、複数の外部ファイルを使用したMerge Sortなど、いくつかのアプローチについて説明しています。ただし、Bentleyが提案する最善の方法は、 ビットフィールドを使用したシングルパスアルゴリズムです 彼はそれをユーモラスに「Wonder Sort」と呼んでいます:)問題になると、40億の数が次のように表現されます。

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

ビットセットを実装するためのコードは単純です。( ソリューションのページ から取得)

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Bentleyのアルゴリズムはファイルを1回通過し、配列内の適切なビットをsettingしてから、上記のtestマクロを使用してこの配列を調べ、欠けている数を見つけます。

利用可能なメモリが0.466 GB未満の場合、Bentleyはkパスアルゴリズムを提案します。これは、利用可能なメモリに応じて入力を範囲に分割します。非常に単純な例を挙げると、1バイト(8個の数字を処理するためのメモリ)しか利用できず、範囲が0から31の場合、これを0から7、8から15、16から22の範囲に分割します。そして32/8 = 4の各パスでこの範囲を処理します。

HTH.

141
vine'th

この問題は、ファイルに含まれていない最小の可能な数を見つける必要があることを指定していないので、入力ファイル自体よりも長い数を生成することができます。 :)

117
Andris

1 GB RAMバリアントの場合は、ビットベクトルを使用できます。 40億ビット== 500 MBのバイト配列を割り当てる必要があります。入力から読み取った各番号について、対応するビットを「1」に設定します。完了したら、ビットを反復して、まだ「0」である最初のものを見つけます。そのインデックスが答えです。

56
Itay Maman

それらが32ビット整数(2 ^ 32に近い〜40億の数の選択からおそらく)であるならば、40億の数のあなたのリストは最大で可能な整数の93%を占めます(4 * 10 ^ 9 /(2 ^ 32))。したがって、各ビットを0に初期化して2 ^ 32ビットのビット配列を作成する場合(これは2 ^ 29バイト〜500 MBのRAMを占有します。バイト= 2 ^ 3ビット= 8ビットを覚えておいてください)整数リストと各intに対して、対応するビット配列要素を0から1に設定します。そして、あなたのビット配列を読み、まだ0である最初のビットを返します。

RAM(10 MB以下)が少ない場合は、この解決策を少し変更する必要があります。 10 MB〜83886080ビットで、0から83886079までのすべての数値に対してビット配列を作成するのに十分です。したがって、intのリストを読むことができます。そして、0から83886079までの間にある#だけをあなたのビット配列に記録してください。番号がランダムに分布している場合圧倒的な確率で(約 10 ^ -2592069 によって100%異なります)、欠落しているintが見つかります。実際、1〜2048の数字(256バイトのRAMのみ)を選択しただけでも、圧倒的なパーセンテージ(99.99999999999999999999999999999999999999999999999999999999999995%)で不足している番号を見つけることができます。

しかし、約40億の数字を持つ代わりに言おう。 2 ^ 32 - 1の数字で10 MB未満のRAMがありました。したがって、整数の範囲が狭い場合でも、数値が含まれていない可能性はわずかしかありません。

リスト内の各intが一意であることが保証されている場合は、数字を合計し、1つの欠けている#を含む合計を減算して(1/2)(2 ^ 32) (2 ^ 32 - 1)= 9223372034707292160不足しているintを検索します。ただし、intが2回発生した場合、このメソッドは失敗します。

しかし、あなたはいつでも分割して征服することができます。単純な方法は、配列を読み、前半(0から2 ^ 31-1)と後半(2 ^ 31、2 ^ 32)にある数を数えることです。次に、より少ない数の範囲を選び、その範囲を半分に分けて繰り返します。 ((2 ^ 31、2 ^ 32)に2つ少ない数があったとすると、あなたの次の検索は(2 ^ 31、3 * 2 ^ 30-1)、(3 * 2 ^ 30、の範囲の数を数えるでしょう。 2 ^ 32)。番号が0の範囲が見つかり、答えが見つかるまで繰り返し続けますO(lg N)〜32回の読み取りが必要です。

その方法は非効率的でした。各ステップでは2つの整数(または4バイト(32ビット)の整数で約8バイトのRAM)しか使用していません。より良い方法はsqrt(2 ^ 32)= 2 ^ 16 = 65536個のビンに分割することでしょう。それぞれのビンは65536個の数になります。各ビンにはカウントを格納するために4バイトが必要です。したがって、2 ^ 18バイト= 256 kBが必要です。したがって、ビン0は(0から65535 = 2 ^ 16-1)、ビン1は(2 ^ 16 = 65536から2 * 2 ^ 16-1 = 131071)、ビン2は(2 * 2 ^ 16 = 131072から3)です。 * 2 ^ 16−1 = 196607)。 Pythonでは、次のようなものがあります。

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

〜40億の整数リストを読みます。そして2 ^ 16個のビンのそれぞれに何個の整数が入っているかを数えて、65536の数字がすべて含まれていないincomplete_binを見つけます。それからあなたは再び40億の整数リストを読みます。しかし今回は、整数がその範囲内にある場合にのみ注目します。あなたがそれらを見つけたときに少し反転します。

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break
45
dr jimbob

なぜそれをそんなに複雑にしますか?ファイルに存在しない整数を要求しますか?

指定された規則によると、格納する必要があるのはファイル内でこれまでに見つかった最大の整数です。ファイル全体が読み取られたら、それより大きい1を返します。

Maxintなどを打つ危険性はありません。規則に従って、整数のサイズやアルゴリズムによって返される数に制限がないからです。

37
Pete

これは、二分検索の変形を使用して非常に小さなスペースで解決することができます。

  1. 0から4294967295までの許容範囲の数字で始めましょう。

  2. 中点を計算します。

  3. ファイルをループ処理して、中間点の値よりも小さい、または大きい数が等しいかどうかを数えます。

  4. 数字が等しくなければ、完了です。中点番号が答えです。

  5. それ以外の場合は、番号が最も少ない範囲を選択し、この新しい範囲でステップ2から繰り返します。

これにはファイル全体で最大32回のリニアスキャンが必要ですが、範囲とカウントを格納するために数バイトのメモリしか使用しません。

これは本質的に Henningの解 と同じですが、16kの代わりに2つのビンを使います。

31
hammar

[0、2 ^x - 1]の範囲にない整数が1つある場合それらすべてをxorします。例えば:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(私はこれが質問に正確に答えるのではないことを知っています、しかしそれは非常によく似た質問に対する良い答えです。)

24
rfrankel

元の質問の現在の表現に基づいて、最も簡単な解決策は次のとおりです。

ファイル内で最大値を見つけ、それに1を加えます。

17
oosterwal

値が大きな集合の一部ではない場合に非常に効率的に絶対値を決定できる確率的 ブルームフィルタ を聞いたことがあるかどうか、彼らは見ているかもしれません。 (ただし、それがセットのメンバーである可能性が高いとしか判断できません。)

16
Paul

BitSetを使用してください。 1バイトあたり8でBitSetにパックされた40億の整数(最大2 ^ 32の整数と仮定)は、2 ^ 32/2 ^ 3 = 2 ^ 29 =約0.5 Gbです。

もう少し詳細を追加するには - 数字を読むたびに、ビットセットの対応するビットを設定します。次に、存在しない最初の番号を見つけるためにBitSetをパスします。実際には、乱数を繰り返し選んでそれが存在するかどうかをテストすることによって、これと同じくらい効果的にこれを行うことができます。

実際にはBitSet.nextClearBit(0)は最初の未設定ビットを教えてくれます。

BitSet APIを見ると、それは0..MAX_INTしかサポートしていないように見えます、それであなたは2つのBitSetを必要とするかもしれません - 1つは+ 5つの数字用、もう1つは - 持っています - しかしメモリ要件は変わりません。

14
dty

サイズ制限がない場合、最も簡単な方法はファイルの長さを取得し、ファイルの長さ+ 1桁のランダムな数字(または単に "11111 ...")を生成することです。利点:ファイルを読む必要さえもなく、メモリ使用量をほぼゼロに抑えることができます。デメリット:何十億もの数字が印刷されます。

ただし、唯一の要因がメモリ使用量の最小化であり、それ以外に重要なものがない場合は、これが最適な解決策になります。それはあなたに「ルールの最悪の乱用」賞を与えるかもしれません。

12
vsz

入力ファイルのサイズを確認してから、anynumber そのサイズのファイルで表現するには大きすぎると出力します。安価なトリックですが、インタビューの問題に対する独創的な解決策であり、メモリの問題をきちんと回避し、技術的にはO(n)です。

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

10を印刷する必要があります ビットカウント - 1、常に2)より大きい ビットカウント。技術的には、あなたが打ち負かさなければならない数はビットカウント - (4×109 - 1)、ファイルには(40億 - 1)個の他の整数があることがわかっているので、たとえ完全な圧縮であっても、それらはそれぞれ少なくとも1ビットを占めます。

9
Justin Morgan
  • 最も簡単な方法は、ファイル内で最小数を見つけ、それより1少ない数を返すことです。これはO(1) storageと、n個の数字のファイルに対してO(n) timeを使います。ただし、番号の範囲が制限されていると失敗し、min-1が非番号になる可能性があります。

  • ビットマップを使用する簡単で直接的な方法はすでに述べられています。そのメソッドはO(n)時間と記憶域を使います。

  • 2 ^ 16のカウントバケットを使用する2パス方法も言及されています。 2 * nの整数を読むので、O(n) timeとO(1) storageを使いますが、2 ^ 16を超える数のデータセットを扱うことはできません。ただし、2パスではなく4パスを実行することで、2 ^ 60 64ビット整数に簡単に拡張できます。メモリに収まるだけのビンを使用し、それに応じてパス数を増やすことで、小さなメモリの使用に簡単に適応できます。この場合、ランタイムはO(n)ではなくなり、代わりにO(n * log n)になります。

  • これまでにrfrankelとircmaxellで言及されていたすべての数をXORする方法は、ltn100のように stackoverflow#35185 で尋ねられた質問に答えます。指摘した。 O(1)記憶域とO(n)ランタイムを使用します。今のところ32ビット整数を仮定すると、XORは7%の確率で異なる数を生成します。理論的根拠:4Gまでの異なる数字をXORしたところで、約ファイル内に300Mがない場合、各ビット位置の設定ビット数は奇数または偶数である可能性が等しくなります。したがって、2 ^ 32個の数がXORの結果と同じように発生する可能性があり、そのうち93%がすでにファイルに入っています。ファイル内の数字がすべて異なるわけではない場合、XORメソッドの成功の可能性が高くなります。

それが不適切に引用されていない限り、トリック質問です。ファイルを1回読んで最大整数nを取得し、n+1を返すだけです。

もちろん、n+1が整数オーバーフローを引き起こす場合に備えて、バックアップ計画が必要です。

8
Mark Ransom

どういうわけか、この問題を読んだ直後に私は対角化を考えました。私は任意に大きい整数を仮定しています。

最初の数字を読みます。 40億ビットになるまで、ゼロビットで左パディングします。先頭(上位)ビットが0の場合、1を出力します。それ以外の場合は0を出力します(実際に左詰めする必要はありません。数字に十分なビットがない場合は1を出力するだけです)。このようにしてファイル全体を続行します。一度に1ビットずつ40億ビットの数を出力しますが、その数はファイル内のものと同じにはなりません。証明:それはn番目の数と同じだった、それで彼らはn番目のビットについては同意するだろうが、彼らは構成によるものではない。

7

完全を期すためだけに、ここに別の非常に単純な解決策があります。これは実行に非常に長い時間がかかる可能性が高いですが、使用するメモリーはごくわずかです。

すべての可能な整数をint_minからint_maxまでの範囲とし、bool isNotInFile(integer)ファイルに特定の整数が含まれていない場合はtrueを返し、それ以外の場合はfalseを返す(ファイル内の各整数との比較)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}
6
deg

整数が存在するかどうかをマークするためにビットフラグを使用できます。

ファイル全体を調べた後、各ビットをスキャンして、番号が存在するかどうかを確認します。

各整数が32ビットであると仮定すると、ビットフラグが立てられれば、それらは1 GBのRAMに都合よく収まるでしょう。

6
Shamim Hafiz

ファイルから空白と数字以外の文字を削除し、1を追加します。これで、元のファイルにリストされていない単一の番号がファイルに含まれます。

CarbonetcによるRedditから。

6
Ashley

10 MBのメモリー制約の場合

  1. 数値をそのバイナリ表現に変換します。
  2. Left = 0、right = 1の二分木を作成します。
  3. バイナリ表現を使用して各番号をツリーに挿入します。
  4. 番号がすでに挿入されている場合、リーフはすでに作成されています。

終了したら、要求された番号を作成するために以前に作成されていないパスを選択してください。

40億の数= 2 ^ 32、つまり10 MBでは不十分な場合があります。

EDIT

両端リーフが作成され、共通の親を持つ場合は最適化が可能です。それらを削除し、親に解決策ではないとフラグを立てることができます。これにより分岐が削減され、メモリの必要性が減少します。

編集II

ツリーを完全に構築する必要もありません。数字が似ている場合にだけ深い枝を作る必要があります。私たちも枝を切るならば、この解決法は実際にうまくいくかもしれません。

5

1 GBのバージョンに答えます。

質問には十分な情報がないので、最初にいくつかの仮定を述べます。

整数は32ビットで、-2,147,483,648から2,147,483,647の範囲です。

擬似コード

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}
5
BobTurbo

私たちが創造的な答えをしている限り、これは別のものです。

入力ファイルを数値順にソートするには、外部ソートプログラムを使用してください。これはあなたが持っているかもしれないどんな量のメモリでも動作するでしょう(必要ならファイルストレージを使います)。ソートされたファイルを読み、欠けている最初の番号を出力してください。

4
Rhialto

Ryanが基本的に言っていたように、ファイルをソートしてから整数を調べてください。値がスキップされると、それが表示されます。

DOWN_VOTERSで_ EDIT:OPはファイルをソートすることができると述べたので、これは有効な方法です。

3
ratchet freak

2128×1018年 + 1(これは(2816×1018年 + 1) - 今日の普遍的な答えにはなり得ないでしょうか?これは16 EBファイルに保持できない数を表します。これは現在のファイルシステムの最大ファイルサイズです。

3

ビット除去

1つの方法はビットを削除することですが、これは実際には結果をもたらさない可能性があります(それはそうではない可能性があります)。疑似コード:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

ビット数

ビット数を追跡​​します。そして、最小量のビットを使用して値を生成します。これも正しい値を生成する保証はありません。

レンジロジック

範囲をリスト順に追跡します(開始順)。範囲は構造によって定義されます。

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

ファイル内の各値を調べて、現在の範囲から削除してみてください。この方法にはメモリの保証はありませんが、かなりうまくいくはずです。

3

私はこれが解決された問題だと思います(上を見てください)、それは尋ねられるかもしれないので覚えておくべき興味深い副次的なケースがあります:

繰り返しのない正確に4,294,967,295(2 ^ 32 - 1)の32ビット整数があり、したがって1つだけが欠けている場合、単純な解決策があります。

積算合計をゼロから開始し、ファイル内の整数ごとに、その整数を32ビットオーバーフローで追加します(事実上、runningTotal =(runningTotal + nextInteger)%4294967296)。完了したら、32ビットオーバーフローを使用して、4294967296/2を現在の合計に追加します。 4294967296からこれを引くと、結果は欠けている整数です。

「1つだけ欠けている整数」問題は、1回の実行、およびデータ専用の64ビットのRAMだけで解決できます(実行中の合計は32、次の整数を読み込むには32)。

推論:整数の結果に必要なビット数を気にしなくても、より一般的な仕様は非常に簡単に一致します。与えられたファイルに含めることができないほど十分に大きい整数を生成するだけです。繰り返しますが、これは絶対に最小限のRAMを占有します。擬似コードを参照してください。

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}
3
Syntaera

32ビットの制約を前提としていない場合は、ランダムに生成された64ビットの数値(悲観主義者の場合は128ビット)を返してください。衝突の可能性は1 in 2^64/(4*10^9) = 4611686018.4です(およそ40億分の1)。あなたはほとんどの場合正しいでしょう!

(冗談...のようなものです。)

2
Peter Gibson

並べ替える必要はありません。それらのサブセットを繰り返しパーティション分割するだけです。

最初のステップはクイックソートの最初のパスのようなものです。整数xの1つを選び、それを使って配列を通過させ、xより小さい値をすべて左に、xより大きい値を右に置くxのどちら側が利用可能なスロットの最大数(リストにない整数)を持っているか探します。これはxの値とその位置を比較することで簡単に計算できます。それからxのその側のサブリストでパーティションを繰り返す。それからsub-subリスト上で利用可能な整数の最大数などを使ってパーティションを繰り返します。

1
Lucas Membrane

おそらく私はこの問題のポイントを完全に失っていますが、あなたは整数のソートされたファイルから欠けている整数を見つけたいですか?

ええと……本当に?そのようなファイルがどのように見えるかについて考えてみましょう。

1 2 3 4 5 6 ...最初に欠けている番号...など.

この問題の解決策は簡単なようです。

1
hacksoncode

いくつかのツリー構造に未訪問の整数の範囲を格納することで、既存のものを読んだ後に欠けている整数を見つけるのを早くすることができます。

あなたは[0..4294967295]を格納することから始め、整数を読むたびにそれが入る範囲をつなぎ合わせ、それが空になったときに範囲を削除します。最後に、範囲内に欠けている整数の正確なセットがあります。したがって、最初の整数として5を見れば、[0..4]と[6..4294967295]があります。

これは、ビットをマーキングするよりもはるかに遅いので、ファイルの下位レベルのツリーを保存できるのであれば、10MBの場合の解決策になります。

そのようなツリーを格納する1つの方法は、キーとしての範囲の始まりと値としての範囲の終わりを持つBツリーです。最悪の場合の使用法は、すべての奇数または偶数の整数を取得するときです。これは、ツリーに2 ^ 31の値または数十GBを格納することを意味します。最良の場合は、ツリー全体に2〜3の整数を使用するだけのソート済みファイルです。

それで、本当に正しい答えではありません、しかし、私はそれをするこの方法に言及したいと思いました。インタビューに失敗したと思います;-)

1
w00t

40億の整数を持つ入力ファイルを考えて、ファイルに含まれていない整数を生成するためのアルゴリズムを提供します。メモリが1 GiBあるとします。あなたがたった10 MiBのメモリしか持っていないなら、あなたがするであろうことをフォローアップしてください。

ファイルのサイズは4 * 109 * 4バイト= 16 GiBです

32ビット符号なし整数の場合

0 <= Number < 2^32
0 <= Number < 4,294,967,296

私の提案した解決策:エラーチェックなしのC++

#include <vector>
#include <fstream>
#include <iostream>
using namespace std;

int main ()
{
    const long SIZE = 1L << 32;

    std::vector<bool> checker(SIZE, false);

    std::ifstream infile("file.txt");  // TODO: error checking

    unsigned int num = 0;

    while (infile >> num)
    {
        checker[num] = true ;
    }

    infile.close();

    // print missing numbers

    for (long i = 0; i < SIZE; i++)
    {
        if (!checker[i])
            cout << i << endl ;
    }

    return 0;
}

複雑

Space ~ 2^32 bits = 2^29 Bytes = 2^19 KB = 2^9 MB = 1/2 GB

Time ~ Single Pass

Completeness ~ Yes
1
Khaled.K

昔の質問ですが、「機能しない」要件について疑問があります。私の意見では、この質問が本の中ではなく他の場所で尋ねられたならば、そこに手掛かりが与えられるべきです - それからそれは賛否両論とすべての可能性を議論するために続きます。多くの場合、就職の面接では質問が足りず、ソフトな要件を知らずに明確な答えを出すことはできないため、困惑したままになっています。 ".

私はそのような質問が合理的な答えを与えるために可能であるかもしれないと思います。

  • 1つのintに4バイトを使用して、すべての数字を新しいファイルにマージソートします。もちろん、これは最初は遅くなります。しかし、それは少ないメモリ量で行うことができます(必ずしもすべてをRAMに保持する必要はありません)。
  • バイナリ検索を使用して、番号が事前ソートされたファイルに存在するかどうかを確認します。値ごとに4バイトのままなので、これは問題ありません。

デメリット:

  • ファイルサイズ
  • 最初のソートが遅い - ただし一度だけ必要

利点:

  • 検索がとても速い

これもまた、本についての非常にいい質問です。しかし、解決すべき問題が完全には分かっていないとき、単一の最良の解決策を求めるとき、私はそれが奇妙な質問だと思います。

0
benjist

これをよく読み過ぎているかもしれませんが、質問には「ファイルに含まれていない整数を生成する」とあります。リストを並べ替えて最大エントリに1を加えるだけです。 Bam、ファイルに含まれていない整数。

0
Sib

私は以下のアルゴリズムを思い付きました。

私の考えは、整数のすべてのファイル全体を一度だけ調べ、ビット位置ごとにその0と1を数えることです。 0と1の量は2 ^(numOfBits)/ 2である必要があります。したがって、量が予想より少ない場合は、得られた数のそれを使用できます。

たとえば、整数が32ビットであるとします。

int[] ones = new int[32];
int[] zeroes = new int[32];

すべての数に対して、32ビットまで繰り返して0または1の値を増やす必要があります。

for(int i = 0; i < 32; i++){
   ones[i] += (val>>i&0x1); 
   zeroes[i] += (val>>i&0x1)==1?0:1;
}

最後に、ファイルが処理された後:

int res = 0;
for(int i = 0; i < 32; i++){
   if(ones[i] < (long)1<<31)res|=1<<i;
}
return res;

注:一部の言語(Javaなど)では、1 << 31は負の数です。したがって、(long)1 << 31が正しい方法です。

0
Timofey