O(n)の時間/空間の複雑さの秘訣は、各サブシーケンスのハッシュを評価することです。配列を考えてみましょうb
:
[b1 b2 b3 ... bn]
ホーナーの方法を使用して、各サブシーケンスのすべての可能なハッシュを評価できます。ベース値B
(両方の配列のどの値よりも大きい)を選択します。
from b1 to b1 = b1 * B^1
from b1 to b2 = b1 * B^1 + b2 * B^2
from b1 to b3 = b1 * B^1 + b2 * B^2 + b3 * B^3
...
from b1 to bn = b1 * B^1 + b2 * B^2 + b3 * B^3 + ... + bn * B^n
前のシーケンスの結果を使用して、O(1)時間で各シーケンスを評価できるため、すべてのジョブにO(n)がかかることに注意してください。
これで配列ができましたHb = [h(b1), h(b2), ... , h(bn)]
。ここで、Hb[i]
はからb1
までのハッシュbi
です。
配列についても同じことを行いますa
が、少しトリックがあります。
from an to an = (an * B^1)
from an-1 to an = (an-1 * B^1) + (an * B^2)
from an-2 to an = (an-2 * B^1) + (an-1 * B^2) + (an * B^3)
...
from a1 to an = (a1 * B^1) + (a2 * B^2) + (a3 * B^3) + ... + (an * B^n)
あるシーケンスから別のシーケンスにステップするときは、前のシーケンス全体にBを乗算し、Bを乗算した新しい値を追加することに注意してください。次に例を示します。
from an to an = (an * B^1)
for the next sequence, multiply the previous by B: (an * B^1) * B = (an * B^2)
now sum with the new value multiplied by B: (an-1 * B^1) + (an * B^2)
hence:
from an-1 to an = (an-1 * B^1) + (an * B^2)
これで配列ができましたHa = [h(an), h(an-1), ... , h(a1)]
。ここで、Ha[i]
はからai
までのハッシュan
です。
これで、nから1までのHa[d] == Hb[d]
すべてのd
値を比較できます。それらが一致する場合は、答えがあります。
注意:これはハッシュ方式であり、値が大きくなる可能性があり、高速指数化方式とモジュラー算術を使用する必要がある場合があります。これにより、(ほとんど)衝突が発生し、この方式は完全に安全ではなくなります。基底B
を本当に大きな素数として選択することをお勧めします(少なくとも配列内の最大値よりも大きい)。また、各ステップで数の制限がオーバーフローする可能性があるためK
、各操作で(モジュロ)を使用する必要があるため、注意が必要です(ここでK
より大きい素数になる可能性がありますB
)。
つまり、2つの異なるシーケンスは同じハッシュを持つ可能性がありますが、2つの等しいシーケンスは常に同じハッシュを持つことになります。
b[1] to b[d]
配列a
計算ハッシュに進み、それがa[1] to a[d]
答えです。そうでない場合は、計算されたハッシュをa[2] to a[d+1]
再利用してハッシュを計算しa[1] to a[d]
ます。しかし、配列内のオブジェクトがローリングハッシュの計算に適しているかどうかはわかりません。