web-dev-qa-db-ja.com

O(n)のサフィックス配列を使用した文字列の最小の辞書式回転

ACM 2003の問題を引用します。

長さn(1 <= n <= 100000)の文字列を考えます。その最小の辞書式回転を決定します。たとえば、文字列「alabala」のローテーションは次のとおりです。

アラバラ

ラバラ

アバラール

バラアラ

アラララブ

ラアラバ

あらばる

その中で最小のものは「aalabal」です。

解決策については、私は suffix array を構築する必要があることを知っており、O(n)でそれを行うことができるとしましょう。私の質問はまだ、どうすればO(n)の最小回転を見つけることができますか? (n =文字列の長さ)

私はこの問題に非常に興味がありますが、それでも解決策が得られません。具体的な実装ではなく、概念と問題の解決方法に興味があります。

注:最小回転とは、英語の辞書と同じ順序であることを意味します。dはwの前にあるため、「dwor」は「Word」の前にあります。

編集:サフィックス配列の構築にはO(N)が必要です

最終編集:解決策を見つけたと思います!!! 2つの文字列をマージした場合はどうなりますか?したがって、文字列が「alabala」である場合、新しい文字列は「alabalaalabala」となり、これのサフィックス配列を作成します(O(2n) = O(n))と最初のサフィックスを取得しましたか?これは正しいと思います。どう思いますか?ありがとうございます!

9
Tomy

長さNの文字列のすべての回転を構築する簡単なトリックは、文字列をそれ自体と連結することです。

次に、この2N長の文字列のすべてのN長の部分文字列は、元の文字列のローテーションです。

「辞書的に最小の」部分文字列の検索は、O(N)ツリー構造で行われます。

5
ardnew

問題:

辞書式に最小の円形部分文字列は、そのようなすべての回転の辞書式順序が最も低い文字列の回転を見つける問題です。たとえば、「bbaaccaadd」の辞書式最小回転は「aaccaaddbb」になります。

ソリューション:

A O(n)時間アルゴリズムは、Jean Pierre Duval(1983)によって提案されました。

Duvalのアルゴリズムは、2つのインデックスijを指定すると、ijで始まる長さj - iの文字列セグメントを比較します(a "duel")。 index + j - iが文字列の長さより大きい場合、セグメントは折り返して形成されます。

たとえば、s = "baabbaba"、i = 5、j = 7について考えます。j-i = 2なので、i = 5で始まる最初のセグメントは "ab"です。 j = 7で始まる2番目のセグメントは、ラップアラウンドによって構成され、これも「ab」です。上記の例のように文字列が辞書式に等しい場合、勝者としてiで始まるものを選択します。つまり、i = 5です。

上記のプロセスは、勝者が1人になるまで繰り返されました。入力文字列の長さが奇数の場合、最後の文字が最初の反復での比較なしで勝ちます。

時間の複雑さ:

最初の反復では、長さ1のn個の文字列を比較し(n/2比較)、2回目の反復では、長さ2のn/2個の文字列を比較します(n/2比較)。長さn/2(n/2の比較)。勝者の数は毎回半分になるため、再帰ツリーの高さはlog(n)であり、O(n log(n))アルゴリズムが得られます。小さなnの場合、これはおよそO(n)です。

スペースの複雑さはO(n)でもあり、最初の反復ではn/2の勝者、2番目の反復ではn/4の勝者などを格納する必要があるためです。(Wikipediaによると、このアルゴリズムでは一定のスペース、私は方法がわかりません)。

Scala実装です。お好きなプログラミング言語に自由に変換してください。

def lexicographicallyMinRotation(s: String): String = {
 @tailrec
 def duel(winners: Seq[Int]): String = {
   if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
   else {
     val newWinners: Seq[Int] = winners
       .sliding(2, 2)
       .map {
         case Seq(x, y) =>
           val range = y - x
           Seq(x, y)
             .map { i =>
               val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
               else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
               (i, segment)
             }
             .reduce((a, b) => if (a._2 <= b._2) a else b)
             ._1
         case xs => xs.head
       }
       .toSeq
     duel(newWinners)
   }
 }

 duel(s.indices)
}
0
Abhijit Sarkar

Suffix配列に含まれる情報は、O(n)に到達するのに十分ではないと確信していますが、O(n log n)に到達するのを助けることができます。このサフィックスのファミリーを検討してください:

a
aba
abacaba
abacabadabacaba
abacabadabacabaeabacabadabacaba
...

次のサフィックスを作成するには、前のサフィックス(たとえばaba)を取得し、まだ使用されていない次の文字を追加してから、前のサフィックスを再度追加します(so aba-> aba c aba)。

次に、これらの文字列を考えます(強調のためにスペースが追加されていますが、文字列の一部ではありません)。

ad abacaba
bd abacaba
cd abacaba

これら3つの文字列の場合、suffix配列の先頭は次のようになります。

a
aba
abacaba
(other suffixes)

見覚えがある?もちろん、これらの文字列は、このサフィックス配列を作成するように調整されています。ここで、開始文字(a、bまたはc)に応じて、「正しい」インデックス(問題の解決策)は、上記のリストの最初、2番目、または3番目のサフィックスになります。

最初の文字の選択は、suffix配列にほとんど影響しません。特に、suffix配列の最初の3つのサフィックスの順序には影響しません。これは、サフィックス配列が非常に類似しているが、「正しい」インデックスが非常に異なるlog n文字列があることを意味します。

ハードプルーフはありませんが、これは、配列の最初の3つのインデックスに対応するローテーションを辞書式順序で比較する以外に選択肢がないことを強く示唆しています。つまり、少なくともO(n log n)このための時間(代替の最初の文字の数-この場合は3-)はlog nであり、2つの文字列の比較にはO(n)時間)かかります。

これは、O(n)アルゴリズムの可能性を除外するものではありません。サフィックス配列がこの実行時間を達成するのに役立つことに疑問を抱くだけです。

0
Alex ten Brink

最小の回転は、suffix配列の一部のサフィックスで始まる回転です。サフィックスは辞書式に順序付けられています。これはあなたに大きなジャンプスタートを与えます:

  • このようなkを取得すると、接尾辞kで始まるローテーションが接尾辞k + 1で始まるローテーションよりも小さいことがわかります。 (最初のものから始まる);
  • O(1)で、「サフィックスkで始まる回転がサフィックスk + 1で始まる回転よりも小さい」という比較を行うことができます。 =サフィックスの長さを比較し、オプションで、1つの文字を別の文字と比較します。

編集:「1つの文字と別の文字」は常にそうであるとは限らず、複数の文字である可能性がありますが、全体として、検索プロセス全体でn個を超える文字を調べないため、O(n)です。

短い証明:サフィックスk + 1がサフィックスkより長い場合にのみ文字を調べ、サフィックスk +の場合は停止してソリューションを見つけます1はサフィックスkよりも短い(そして、サフィックスkが目的のものであることがわかります)。したがって、文字を調べるのは、サフィックスの(長さ方向の)昇順のシーケンスのときだけです。余分な文字のみを検査するため、n文字を超える検査はできません。

EDIT2:このアルゴリズムは、「接尾辞配列に2つの隣接接尾辞があり、前のものが後続のものよりも短い場合、前のものが後続の接頭辞である」という事実に依存しています。これが真実でない場合は、ごめんなさい。

EDIT3:いいえ、それは保持されません。 「abaaa」にはサフィックステーブル「a」、「aa」、「aaa」、「abaaa」、「baaa」があります。しかし、おそらくこの考え方が最終的には解決策につながる可能性があります。詳細をさらに洗練させる必要があるだけです。主な質問は、前述の比較を少しの文字数で調べることができるかどうかです。つまり、O(n)完全にそうです)、これはどういうわけか可能であると信じています。方法を教えてください。

0
herby