これは完全な答えではありませんが、この問題が難しい理由を明らかにするのに役立つはずです。
最も一般的な要素を見つけるために配列を (ある順序で) 1 回スイープするアルゴリズムを設計したいとします。アルゴリズムの実行中、一部のデータ構造を保持することが許可されていますS
。S
にどのくらいの情報が必要か、そしてそれをO(1)
メモリに格納できるかどうかを見てみましょう。
アルゴリズムがk
配列の最初の要素を処理したとします。S
これで、範囲内で最も一般的な要素がわかりますa[0..k]
。ただし、k+1
最初の要素がわかっているとしたら、その範囲で最も一般的な要素もわかっていることになりますa[0..k+1]
。それができない場合、 が の場合、アルゴリズムは機能しませn
んk+1
。より一般的には、要素a[k..m]
との知識があれS
ば、 で最も一般的な要素がわかりa[0..m]
ます。
上記の引数を使用して、 から情報を抽出できますS
。範囲内の整数で作業しているとします[0,u]
(元の配列がスペースを取る場合、何らかの範囲が必要ですO(n)
)。元の最も一般的な要素が5
である場合、最も一般的な要素が変更されるまで を追加0
します。c
ゼロを取った場合は、 よりも多くのa[0..k]
が含まれている必要があります。この引数を繰り返すと、各要素がに何回存在したかを正確に知るために解くことができる多くの線形方程式が得られます。c
5
0
[0,u]
a[0..k]
これは、スイープを行うすべてのデータ構造が、見られたすべての要素のカウントを (圧縮された方法で) 保存する可能性があることを示しています。数学に興味がある場合は、見分けがつかないアイテムを区別可能なビンに分割する方法の数のログである、見た後に保存されたn
数値です。以上です。log(n+u-1 choose n)
n
u
log(u^n/n!) >= nlogu-nlogn
結論: 配列のパスを 1 回だけ実行するアルゴリズムは、これまでに見たすべてのカウントを格納するのに必要なだけのメモリを使用する必要があります。n
これに比べて小さい場合は、記憶の単語u
を格納することに相当します。n
(追加のメモリの代わりに、既存の配列を上書きすることもできます)。
ここで探索することは他にもたくさんあります。たとえば、複数のパスが上記の引数にどのように影響するか。ただし、この時点で停止する必要があると思います:)、しかし、大きな を持つ線形時間アルゴリズムが余分なメモリu
で逃げることができるとは思えません。O(1)