この問題はAmazonのインタビューで私に尋ねられました-
正の整数の配列が与えられた場合、配列の数値の合計から形成できない最小の正の整数を見つける必要があります。
例:
Array:[4 13 2 3 1]
result= 11 { Since 11 was smallest positive number which can not be formed from the given array elements }
私がしたことは:
しかし、これはnlog(n)ソリューションでした。
インタビュアーはこれに満足せず、O(n log n)時間未満で解決策を尋ねました。
区間[2のすべての整数を考慮してください私 .. 2i + 1 -1]。そして、2未満のすべての整数を想定します。私 与えられた配列からの数の合計から形成することができます。また、2未満のすべての数値の合計であるCがすでにわかっているとします。私。 C> = 2の場合i + 1 -1、この間隔のすべての数値は、指定された数値の合計として表すことができます。それ以外の場合は、間隔[2私 .. C + 1]には、指定された配列からの任意の数が含まれます。そして、そのような数がない場合は、C +1が検索対象です。
アルゴリズムのスケッチは次のとおりです。
S[int_log(x)] += x
。foreach i: C[i] = C[i-1] + S[i]
_。i = int_log(x) - 1; B[i] |= (x <= C[i] + 1)
が含まれています。B[]
_の対応する要素を見つけます。手順3を適用できる理由が明らかでない場合は、以下がその証拠です。 2から任意の数を選択してください私 およびC、次に2未満のすべての数値を順次減算します。私 降順。最終的には、最後に減算された数値よりも小さい数値またはゼロのいずれかが得られます。結果がゼロの場合は、減算されたすべての数値を合計するだけで、選択された数値の表現が得られます。結果がゼロ以外で、最後に減算された数よりも小さい場合、この結果も2未満です。私なので、それは「表現可能」であり、減算された数値はどれもその表現に使用されません。これらの減算された数値を加算すると、選択された数値の表現になります。これは、間隔を1つずつフィルタリングする代わりに、Cのint_logに直接ジャンプすることで、一度に複数の間隔をスキップできることも示唆しています。
時間計算量は、関数int_log()
によって決定されます。これは、整数の対数または数値の最上位セットビットのインデックスです。命令セットに整数の対数またはそれに相当するもの(先行ゼロのカウント、または浮動小数点数のトリック)が含まれている場合、複雑さはO(n)です。それ以外の場合は、多少のハッキングを使用してint_log()
をO(log log U)に実装し、O(n * log log U)時間の複雑さを取得できます。 (ここで、Uは配列の最大数です)。
ステップ1(合計の更新に加えて)も指定された範囲の最小値を更新する場合、ステップ4はもう必要ありません。 C [i]をMin [i +1]と比較するだけです。これは、入力配列を1回パスするだけでよいことを意味します。または、このアルゴリズムを配列ではなく数値のストリームに適用することもできます。
いくつかの例:
_Input: [ 4 13 2 3 1] [ 1 2 3 9] [ 1 1 2 9]
int_log: 2 3 1 1 0 0 1 1 3 0 0 1 3
int_log: 0 1 2 3 0 1 2 3 0 1 2 3
S: 1 5 4 13 1 5 0 9 2 2 0 9
C: 1 6 10 23 1 6 6 15 2 4 4 13
filtered(C): n n n n n n n n n n n n
number in
[2^i..C+1]: 2 4 - 2 - - 2 - -
C+1: 11 7 5
_
多倍長入力数の場合、このアプローチにはO(n * log M)時間とO(log M)スペースが必要です。ここで、Mは配列内の最大数です。すべての数値を読み取るだけで同じ時間が必要です(最悪の場合、すべての数値が必要になります)。
それでも、この結果はO(n * log R)に改善される可能性があります。ここで、Rはこのアルゴリズムによって検出された値です(実際には、出力の影響を受けやすいバリアント)。この最適化に必要な唯一の変更は、整数を一度に処理するのではなく、桁ごとに処理することです。最初のパスは各数値の下位ビット(ビット0..63など)を処理し、2番目のパス-次のビット(など)を処理します。 64..127)など。結果が見つかった後は、すべての上位ビットを無視できます。また、これによりスペース要件がO(K)数値に減少します。ここで、Kはマシンワードのビット数です。
この問題を時間O(n + Sort)で解決するための美しいアルゴリズムがあります。ここで、Sortは、入力配列のソートに必要な時間です。
アルゴリズムの背後にある考え方は、配列を並べ替えてから、次の質問をすることです。配列の最初のk要素を使用して作成できない最小の正の整数は何ですか?次に、配列を左から右に順方向にスキャンし、作成できない最小の数が見つかるまで、この質問に対する回答を更新します。
仕組みは次のとおりです。最初は、作成できない最小の数は1です。次に、左から右に向かって、次の手順を実行します。
candidate
と呼ぶ)で作成できない最小数がわかっており、値A[k]
を調べています。したがって、candidate - A[k]
は、配列の最初のk個の要素で実際に作成できる数でなければなりません。そうでない場合、candidate - A[k]
は、配列の最初のk個の数字で作成できないとされる最小の数よりも小さい数になります。さらに、candidate
からcandidate + A[k]
までの範囲内の任意の数値を作成できます。これは、1からA[k]
までの範囲の任意の数値で開始し、それにcandidate - 1
を追加できるためです。したがって、candidate
をcandidate + A[k]
に設定し、k
をインクリメントします。擬似コード:
Sort(A)
candidate = 1
for i from 1 to length(A):
if A[i] > candidate: return candidate
else: candidate = candidate + A[i]
return candidate
これは[4, 13, 2, 1, 3]
でのテスト実行です。配列を並べ替えて[1, 2, 3, 4, 13]
を取得します。次に、candidate
を1に設定します。次に次のようにします。
candidate
= 1:candidate
なので、candidate = candidate + A[1] = 2
を設定しますcandidate
= 2:candidate
なので、candidate = candidate + A[2] = 4
を設定しますcandidate
= 4:candidate
なので、candidate = candidate + A[3] = 7
を設定しますcandidate
= 7:candidate
なので、candidate = candidate + A[4] = 11
を設定しますcandidate
= 11:candidate
なので、candidate
(11)を返します。したがって、答えは11です。
ここでのランタイムはO(n + Sort)です。これは、ソート以外では、ランタイムがO(n)だからです。ヒープソートを使用してO(n log n)時間で明確に並べ替えることができます。数値の上限がわかっている場合は、基数ソートを使用して時間O(n log U)(Uは可能な最大数)で並べ替えることができます。 Uが固定定数の場合(たとえば、109)、基数ソートは時間O(n)で実行され、このアルゴリズム全体も時間O(n)で実行されます。
お役に立てれば!
ビットベクトルを使用して、これを線形時間で実現します。
空のビットベクトルから始めますb。次に、配列の各要素kに対して、次のようにします。
b = b | b << k | 2 ^(k-1)
明確にするために、i番目の要素は数値iを表すために1に設定され、| k
はk番目の要素を1に設定しています。
配列の処理が終了すると、bの最初のゼロのインデックスが答えになります(右から数えて1から始まります)。
最初のゼロ:位置11。