O(N)
これは、O(N)
スペースを使用する簡単なソリューションです。入力リストを非負の数に制限していて、リストにない最初の非負の数を見つけたいと想定しています。
- リストの長さを見つけます。としましょう
N
。
N
allに初期化されたブール値の配列を割り当てますfalse
。
X
リスト内の各数値について、X
が未満の場合は、配列の要素をN
に設定X'th
しtrue
ます。
- インデックスから始めて配列をスキャン
0
し、最初の要素であるを探しfalse
ます。false
インデックスI
で最初のものを見つけたら、それI
が答えです。それ以外の場合(つまり、すべての要素がである場合true
)、答えはN
です。
実際には、「N
ブール値の配列」はおそらく、byte
またはint
配列として表される「ビットマップ」または「ビットセット」としてエンコードされます。これにより、通常、使用するスペースが少なくなり(プログラミング言語によって異なります)、最初のスキャンをfalse
より迅速に実行できます。
これが、アルゴリズムが機能する方法/理由です。
N
リスト内の番号が明確でないか、1つ以上の番号がN
。より大きいと仮定します。これは、リストにない範囲に少なくとも1つの数値が存在する必要があることを意味し0 .. N - 1
ます。したがって、最小の欠落数を見つける問題は、N
。未満の最小の欠落数を見つける問題に還元する必要があります。これはN
、答えにはならないため、...以上の数値を追跡する必要がないことを意味します。
前の段落の代わりに、リストはからの番号の順列です0 .. N - 1
。この場合、ステップ3は配列のすべての要素をに設定しtrue
、ステップ4は最初の「欠落している」数がであると通知しN
ます。
アルゴリズムの計算の複雑さは、O(N)
比例定数が比較的小さいことです。リストを2回線形パスするか、リストの長さが最初からわかっている場合は1回だけパスします。リスト全体をメモリに保持することを表す必要はないため、アルゴリズムの漸近的なメモリ使用量は、ブール値の配列を表すために必要なものです。すなわちO(N)
ビット。
(対照的に、メモリ内の並べ替えまたはパーティショニングに依存するアルゴリズムは、リスト全体をメモリ内で表現できることを前提としています。質問された形式では、O(N)
64ビットワードが必要になります。)
@Jornは、ステップ1から3は、ソートのカウントのバリエーションであるとコメントしています。ある意味で彼は正しいですが、違いは重要です。
- カウントソートには、(少なくとも)
Xmax - Xmin
カウンターの配列が必要です。ここXmax
で、はリスト内の最大数であり、はリスト内Xmin
の最小数です。各カウンターは、N個の状態を表すことができなければなりません。つまり、バイナリ表現を想定すると、(少なくとも)整数型のceiling(log2(N))
ビットが必要です。
- 配列サイズを決定するには、カウントソートでリストを最初に通過して
Xmax
とを決定する必要がありXmin
ます。
- したがって、最悪の場合の最小スペース要件は
ceiling(log2(N)) * (Xmax - Xmin)
ビットです。
対照的に、上記のアルゴリズムN
では、最悪の場合と最良の場合にビットが必要です。
ただし、この分析により、アルゴリズムがゼロを探してリストを最初に通過した場合(および必要に応じてリスト要素をカウントした場合)、ゼロが見つかった場合はスペースをまったく使用せずに迅速な回答が得られるという直感につながります。リスト内に少なくとも1つのゼロが見つかる可能性が高い場合は、これを行う価値があります。そして、この余分なパスは全体的な複雑さを変えません。
編集:ビットとビットマップを使用した元の説明が混乱していると思われるため、アルゴリズムの説明を「ブール値の配列」を使用するように変更しました。